Почему виртуальный звонок стоит дорого? Потому что вы просто не знаете цель ветвления, пока код не будет выполнен во время выполнения. Даже современные процессоры по-прежнему отлично справляются с виртуальными и косвенными вызовами. Нельзя просто сказать, что это ничего не стоит, потому что у нас просто более быстрый процессор. Нет, это не так.
1. Как мы можем сделать это быстро?
У вас уже есть достаточно глубокое понимание проблемы. Но единственное, что я могу сказать, что если вызов виртуальной функции легко предсказать, то вы можете выполнить оптимизацию на уровне программного обеспечения. Но если это не так (т. Е. Вы действительно не представляете, какова будет цель виртуальной функции), то я не думаю, что сейчас есть хорошее решение. Даже в случае процессора трудно предсказать в таком крайнем случае.
На самом деле, компиляторы, такие как PGO (оптимизация с профилированием) в Visual C ++, имеют спекуляция виртуальными вызовами оптимизация ( Link ). Если результат профилирования может перечислять горячие виртуальные целевые функции, он переводится в прямой вызов , который может быть встроенным. Это также называется девиртуализация . Его также можно найти в некоторых динамических оптимизаторах Java.
2. Тем, кто говорит, что это не нужно
Если вы используете языки сценариев, C # и заботитесь об эффективности кодирования, да, это бесполезно. Тем не менее, любой, кто стремится сохранить один цикл, чтобы получить лучшую производительность, то косвенная ветвь все еще является важной проблемой. Даже новейшие процессоры не годятся для обработки виртуальных вызовов. Хорошим примером может служить виртуальная машина или интерпретатор, которые обычно имеют очень большой коммутатор. Его производительность в значительной степени связана с правильным прогнозом косвенного перехода. Таким образом, вы не можете просто сказать, что это слишком низкий уровень или нет необходимости. Есть сотни людей, которые пытаются улучшить производительность в нижней части. Вот почему вы можете просто игнорировать такие детали:)
3. Некоторые скучные компьютерные архитектурные факты, связанные с виртуальными функциями
dsimcha написал хороший ответ о том, как процессор может эффективно обрабатывать виртуальные вызовы. Но это не совсем правильно. Во-первых, все современные ЦП имеют предиктор ветвления, который буквально предсказывает результаты ветвления, чтобы увеличить пропускную способность конвейера (или, больше параллелизма на уровне команд, или ILP . Я даже могу сказать, что производительность однопоточного ЦП исключительно в зависимости от того, сколько вы можете извлечь ILP из одного потока. Прогноз ветвления является наиболее важным фактором для получения более высокого ILP).
В предсказании ветвлений есть два предсказания: (1) направление (т. Е. Ветвь взята - или не взята? Бинарный ответ) и (2) цель ветвления (т. Е. Куда я пойду? Это не бинарный ответ ). Основываясь на прогнозе, процессор умозрительно выполняет код. Если предположение неверно, то процессор откатывается и перезапускается из неправильно предсказанной ветви. Это полностью скрыто от взгляда программиста. Таким образом, вы на самом деле не знаете, что происходит внутри ЦП, если вы не профилируете с помощью VTune, который дает показатели ошибочного прогнозирования ветвлений.
В общем, прогнозирование направления ветвления является очень точным (95% +), но все еще сложно предсказать цели перехода, особенно виртуальные вызовы и случай переключения (т. Е. Таблицу переходов). Вызов Vrtual - это косвенная ветвь , которая требует большей загрузки памяти, а также CPU требует прогнозирования цели ветвления. Современные процессоры, такие как Intel Nehalem и AMD Phenom, имеют специализированную таблицу косвенных переходов.
Однако я не думаю, что поиск vtable влечет за собой много накладных расходов. Да, это требует большей загрузки памяти, что может привести к потере кеша. Но, как только vtable загружается в кеш, тогда он в значительной степени попадает в кеш. Если вас также беспокоит эта стоимость, вы можете заранее добавить код для предварительной загрузки vtable. Но настоящая трудность вызова виртуальных функций заключается в том, что ЦП не может сделать большую работу, чтобы предсказать цель виртуального вызова, что может часто приводить к истощению конвейера из-за неправильного предсказания цели.