Виртуальные функции очень эффективны. При принятии указателей на 32 бита расположение памяти приблизительно:
classptr -> [vtable:4][classdata:x]
vtable -> [first:4][second:4][third:4][fourth:4][...]
first -> [code:x]
second -> [code:x]
...
classptr указывает на память, которая обычно находится на "куче", иногда на стеке, и запускается с четырехбайтового указателя на vtable для того класса. Но важной вещью помнить является само vtable, не выделенная память. Это - статический ресурс, и все объекты того же типа класса укажут на точно та же ячейка памяти для их vtable массива. Обращение к различным экземплярам не вытянет различные ячейки памяти в кэш L2.
Этот пример от msdn показывает vtable для класса A с виртуальным func1, func2, и func3. Ничто больше чем 12 байтов. Существует хороший шанс, vtables различных классов также будет физически смежен в скомпилированной библиотеке (Вы захотите проверить, что это, Вы особенно заинтересованы), который мог увеличить эффективность кэша тщательно.
CONST SEGMENT
??_7A@@6B@
DD FLAT:?func1@A@@UAEXXZ
DD FLAT:?func2@A@@UAEXXZ
DD FLAT:?func3@A@@UAEXXZ
CONST ENDS
другая проблема производительности была бы инструкцией наверху вызова через vtable функцию. Это также очень эффективно. Почти идентичный вызову невиртуальной функции. Снова от пример от msdn:
; A* pa;
; pa->func3();
mov eax, DWORD PTR _pa$[ebp]
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR _pa$[ebp]
call DWORD PTR [edx+8]
В этом примере ebp, указателе базы стекового фрейма, имеет переменную A* pa
при нулевом смещении. Регистр eax загружается значением в местоположении [ebp], таким образом, это имеет*, и edx загружается значением в местоположении [eax], таким образом, это имеет vtable класс A. Тогда ecx загружается [ebp], потому что ecx представляет "это", это теперь содержит*, и наконец вызов выполняется к значению в местоположении [edx+8], который является третьим функциональным адресом в vtable.
, Если бы этот вызов функции не был виртуальным, mov eax и mov edx не были бы необходимы, но разница в производительности была бы неизмеримо небольшой.
Если бы приложение AI не требует большого перемалывания чисел, я не волновался бы о недостатке производительности виртуальных функций. Будет крайний хит производительности, только если они появляются в сложных вычислениях, которые неоднократно оцениваются. Я не думаю, что можно вынудить виртуальную таблицу остаться в кэше L2 также.
существует несколько оптимизации, доступной для виртуальных функций,
С современными, выглядящими вперед, несколькими диспетчеризирующими центральными процессорами издержки для виртуальной функции могли бы хорошо быть нулем. Nada. Zip.
Стоимость является более или менее тем же, чем нормальные функции в наше время для недавних CPU, но они не могут быть встроены. При вызове функции миллионы времен влияние может быть значительным (попытайтесь назвать миллионы времен той же функцией, например, однажды со встроенным однажды без, и Вы будете видеть, что это может быть вдвое медленнее, если сама функция делает что-то простое; это не теоретический случай: это довольно характерно для большого числового вычисления).
Редко необходимо волноваться о кэше в отношении таких наиболее часто используемых объектов, так как они выбраны однажды и сохранены там.
Кэш является только обычно проблемой при контакте с большими структурами данных что также:
Дела как Vtables обычно не идут быть производительностью/кэшем/проблемой памяти; обычно существует только один Vtable на тип объекта, и объект содержит указатель на Vtable вместо самого Vtable. Таким образом, если у Вас нет нескольких тысяч типов объектов, я не думаю, что Vtables собираются перегрузить Ваш кэш.
1), между прочим, то, почему функции как memcpy используют инструкции по потоковой передаче обхода кэша как movnt (dq|q) для чрезвычайно большого (мультимегабайт) вводы данных.
Виртуальные вызовы не представляют намного большие издержки по нормальным функциям. Хотя, самая большая потеря - то, что виртуальная функция, когда названо полиморфно не может быть встроена. И встраивание будет в большом количестве ситуаций представлять некоторое реальное усиление в производительности.
Что-то можно сделать для предотвращения потерь того средства в некоторых ситуациях, должен объявить функцию, встроенную виртуальный.
Class A {
inline virtual int foo() {...}
};
И когда Вы в точке кода, Вы УВЕРЕНЫ в типе называемого объекта, можно выполнить встроенный вызов, который избежит полиморфной системы и позволит встроить компилятором.
class B : public A {
inline virtual int foo()
{
//...do something different
}
void bar()
{
//logic...
B::foo();
// more logic
}
};
В этом примере, вызов к foo()
будет выполнен неполиморфный и связанный к B
реализация foo()
. Но сделайте это только, когда Вы знаете наверняка, каков тип экземпляра, потому что автоматическая функция полиморфизма закончится, и это не очень очевидно для более поздних читателей кода.
Я укрепляю все ответы, в которых говорится в действительности:
то, Что Вы хотите знать:
Некоторые профилировщики могут дать Вам эту информацию косвенно. Они должны подвести итог на уровне оператора, но эксклюзивный из времени, проведенного в самом методе.
Моя любимая техника состоит в том, чтобы просто приостановить его неоднократно под отладчиком.
, Если время, проведенное в процессе вызовов виртуальной функции, является значительным, как, говорят, что 20%, то в среднем 1 из 5 образцов покажет, у основания стека вызовов, в окне дизассемблирования, инструкциях для следующего указатель виртуальной функции.
, Если Вы на самом деле не видите, что, это не проблема.
В процессе, Вы будете, вероятно, видеть другие вещи выше стек вызовов, которые на самом деле не нужны и могли сохранить Вас много времени.
Решением динамического полиморфизма мог быть статический полиморфизм, применимый, если Ваши типы известны в типе компиляции: CRTP (Любопытно повторяющийся шаблонный шаблон).
http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
объяснение на Википедию является достаточно четким, и возможно Это могло справка Вы , если бы Вы действительно определили , виртуальные вызовы метода были источником узких мест производительности.
Можно реализовать полиморфизм во времени выполнения с помощью виртуальных функций и во время компиляции при помощи шаблонов. Можно заменить виртуальные функции шаблонами. Смотрите на эту статью для получения дополнительной информации - http://www.codeproject.com/KB/cpp/SimulationofVirtualFunc.aspx
Виртуальные функции имеют тенденцию быть косвенный вызов функции и поиск. На некоторых платформах это быстро. На других, например, одна популярная архитектура PPC, используемая в консолях, это не настолько быстро.
Оптимизация обычно вращается вокруг выражения изменчивости выше в стеке вызовов так, чтобы Вы не должны были вызывать виртуальную функцию многократно в горячих точках.
Единственная оптимизация, о которой я могу думать, является JIT-компилятором Java. Если я понимаю его правильно, это контролирует вызовы, когда код работает, и если большинство вызовов переходит к конкретной реализации только, это вставляет условный переход в реализацию, когда класс является правильным. Таким образом, большую часть времени, нет никакого vtable поиска. Конечно, для редкого случая, когда мы передаем различный класс, vtable, все еще используется.
я не знаю ни о каком компиляторе/времени выполнения C++, который использует эту технику.
Вы на самом деле представили и нашли, где, и чему нужна оптимизация?
Работа над фактической оптимизацией виртуальной функции звонит, когда Вы нашли, что они на самом деле - узкое место.
Разделите 5.3.3 из эти , готовят Технический Отчет на Производительности C++ , полностью посвящен издержкам виртуальных функций.
Как уже указано другими ответами, фактические издержки вызова виртуальной функции являются довольно маленькими. Это может иметь значение в жестком цикле, где это называют миллионами времен в секунду, но это редко - грандиозное предприятие.
Однако это может все еще оказать большее влияние, в котором компилятору более трудно оптимизировать. Это не может встроить вызов функции, потому что это не знает во время компиляции, какая функция будет вызвана. Это также делает некоторую глобальную оптимизацию тяжелее. И какого количества производительности это стоит Вам? Это зависит. Это обычно - ничто для волнения о, но существуют случаи, где это может означать значительный хит производительности.
И конечно это также зависит от архитектуры ЦП. На некоторых это может стать довольно дорогим.
, Но стоит иметь в виду, что любой вид полиморфизма во время выполнения несет более или менее те же издержки. Реализация той же функциональности через операторы переключения или подобный, для выбора между многими возможными функциями не может быть более дешевой.
единственный надежный способ оптимизировать это было бы то, если Вы могли бы переместить часть работы ко времени компиляции. Если возможно реализовать часть его как статический полиморфизм, некоторое ускорение может быть возможным.
, Но сначала, удостоверьтесь, что у Вас есть проблема. Код является на самом деле слишком медленным, чтобы быть приемлемым? Во-вторых, узнайте то, что заставляет его замедлиться через профилировщика. И в-третьих, зафиксируйте его.
Статический полиморфизм, как здесь ответили некоторые пользователи. Например, WTL использует этот метод. Четкое объяснение реализации WTL можно найти на http://www.codeproject.com/KB/wtl/wtl4mfc1.aspx#atltemplates