Віртуальні методи зазвичай реалізуються за допомогою так званих таблиць віртуальних методів (короткі vtable), в яких зберігаються покажчики функцій. Це додає непрямості до фактичного виклику (треба отримати адресу функції для виклику з vtable, а потім зателефонувати - на відміну від просто виклику його вперед). Звичайно, на це потрібно певний час і ще якийсь код.
Однак це не обов'язково є основною причиною повільності. Справжня проблема полягає в тому, що компілятор (як правило / зазвичай) не може знати, яка функція буде викликана. Таким чином, він не може вбудовувати його чи виконувати будь-які інші подібні оптимізації. Це одне може додати десяток безглуздих інструкцій (підготовка регістрів, виклик, потім відновлення стану) і може перешкоджати іншим, здавалося б, не пов'язаним оптимізаціям. Більше того, якщо ви розгалужуєтесь як божевільний, викликаючи безліч різних реалізацій, ви страждаєте одними і тими ж зверненнями, які ви будете страждати від розгалуження, як божевільні, іншими способами: кеш-пам'ять і передбачувач гілок вам не допоможуть, гілки займуть більше часу, ніж ідеально передбачувана відділення.
Великий, але : Ці хіти на виставу зазвичай занадто крихітні, щоб мати значення. Вони варто розглянути, якщо ви хочете створити високоефективний код і розглянути можливість додавання віртуальної функції, яка б викликалася з тривожною частотою. Тим НЕ менше, також мати на увазі , що заміна викликів віртуальних функцій з іншими засобами розгалуження ( if .. else
, switch
, покажчики на функції і т.д.) не вирішує основне питання - це дуже добре може бути повільніше. Проблема (якщо вона взагалі існує) полягає не у віртуальних функціях, а у (непотрібній) непрямості.
Редагувати: Різниця в інструкціях про дзвінки описана в інших відповідях. В основному, код для статичного ("нормального") дзвінка:
- Скопіюйте деякі регістри в стек, щоб дозволена функція використовувала ці регістри.
- Скопіюйте аргументи у заздалегідь задані місця, щоб викликана функція змогла їх знайти незалежно від місця її виклику.
- Натисніть зворотну адресу.
- Відділення / перехід до коду функції, який є адресою компіляції та, отже, компілятором / посилачем жорстко кодується у двійковій формі.
- Отримати повернене значення із заздалегідь визначеного місця та відновити регістри, які ми хочемо використовувати.
Віртуальний виклик робить саме те саме, за винятком того, що адреса функції не відома під час компіляції. Натомість пара інструкцій ...
- Отримайте вказівник vtable, який вказує на масив функціональних покажчиків (адреси функцій), по одному для кожної віртуальної функції, від об'єкта.
- Отримайте потрібну адресу функції з vtable в регістр (індекс, де зберігається правильна адреса функції, визначається під час компіляції).
- Перейти до адреси в цьому реєстрі, а не переходити на жорстко кодовану адресу.
Що стосується гілок: гілка - це все, що переходить до іншої інструкції, а не просто дозволяти виконувати наступну інструкцію. Це включає if
, switch
частини різних циклів, виклики функцій тощо, а іноді компілятор реалізує речі, які, здається, не розгалужуються таким чином, що насправді потребує гілки під кришкою. Див. Чому обробка відсортованого масиву швидша, ніж несортований масив? чому це може бути повільним, що робити процесори, щоб протистояти цьому сповільненню, і як це не все для лікування.