Может кто-нибудь подробно объяснить, как именно работает виртуальная таблица и какие указатели связаны при вызове виртуальных функций.
Если они на самом деле медленнее, можете ли вы показать, что время выполнения виртуальной функции превышает обычные методы класса? Легко потерять отслеживание того, как / что происходит, не видя некоторого кода.
Ответы:
Виртуальные методы обычно реализуются через так называемые таблицы виртуальных методов (для краткости vtable), в которых хранятся указатели функций. Это добавляет косвенность к фактическому вызову (нужно получить адрес функции для вызова из виртуальной таблицы, затем вызвать ее - вместо того, чтобы просто вызывать ее прямо сейчас). Конечно, это займет некоторое время и еще немного кода.
Однако это не обязательно является основной причиной медлительности. Реальная проблема заключается в том, что компилятор (обычно / обычно) не может знать, какая функция будет вызвана. Поэтому он не может встроить его или выполнить другие подобные оптимизации. Одно это может добавить дюжину бессмысленных инструкций (подготовка регистров, вызов, затем восстановление состояния после этого) и может помешать другим, казалось бы, не связанным оптимизациям. Более того, если вы разветвляетесь как сумасшедший, вызывая много разных реализаций, вы получаете те же хиты, что и ветвление, как от сумасшедшего, с помощью других средств: кеш и предиктор ветвлений вам не помогут, ветки займут больше времени, чем совершенно предсказуемые ветка.
Большое но : эти хиты производительности, как правило, слишком малы, чтобы иметь значение. Им стоит подумать, хотите ли вы создать высокопроизводительный код и добавить виртуальную функцию, которая будет вызываться с пугающей частотой. Однако следует также помнить, что замена вызовов виртуальных функций другими средствами ветвления (
if .. else
,switch
указателями функций и т. Д.) Не решит фундаментальную проблему - она может быть очень медленной. Проблема (если она вообще существует) не в виртуальных функциях, а в (ненужном) косвенном обращении.Изменить: Разница в инструкциях вызова описана в других ответах. По сути, код для статического («нормального») вызова:
Виртуальный вызов делает то же самое, за исключением того, что адрес функции не известен во время компиляции. Вместо этого пара инструкций ...
Что касается ветвей: Ветвь - это все, что переходит к другой инструкции вместо того, чтобы просто выполнить следующую инструкцию. Это включает в себя
if
,switch
, части различных циклов, вызовов функций и т.д. , а иногда компилятор реализует вещи , которые , кажется, не ветви таким образом , что на самом деле нуждается в отделении под капотом. См. Почему обработка отсортированного массива быстрее, чем несортированного массива? почему это может быть медленным, что процессоры делают, чтобы противостоять этому замедлению, и как это не панацея.источник
virtual
.Вот некоторый фактический дизассемблированный код из вызова виртуальной функции и не виртуального вызова, соответственно:
Вы можете видеть, что виртуальный вызов требует трех дополнительных инструкций для поиска правильного адреса, тогда как адрес не виртуального вызова может быть скомпилирован в.
Тем не менее, обратите внимание, что большую часть времени дополнительное время поиска можно считать незначительным. В ситуациях, когда время поиска было бы значительным, как в цикле, значение обычно можно кэшировать, выполнив первые три инструкции перед циклом.
Другая ситуация, когда время поиска становится значительным, - это если у вас есть коллекция объектов, и вы просматриваете виртуальную функцию для каждого из них. Тем не менее, в этом случае вам понадобятся некоторые средства выбора функции для вызова в любом случае, и поиск в виртуальной таблице является таким же хорошим средством, как и любой другой. Фактически, поскольку код поиска vtable очень широко используется, он сильно оптимизирован, поэтому попытка обойти его вручную имеет хорошие шансы, что приведет к снижению производительности.
источник
-0x8(%rbp)
, о мой ... этот синтаксис AT & T.Медленнее чем что ?
Виртуальные функции решают проблему, которая не может быть решена прямыми вызовами функций. В общем, вы можете сравнить только две программы, которые вычисляют одно и то же. «Этот трассировщик лучей быстрее, чем этот компилятор» не имеет смысла, и этот принцип обобщает даже такие мелочи, как отдельные функции или конструкции языка программирования.
Если вы не используете виртуальную функцию для динамического переключения на фрагмент кода, основанный на элементах данных, например на типе объекта, вам придется использовать что-то еще, например,
switch
оператор, чтобы выполнить то же самое. Это что-то еще имеет свои накладные расходы, плюс последствия для организации программы, которые влияют на ее ремонтопригодность и глобальную производительность.Обратите внимание, что в C ++ вызовы виртуальных функций не всегда являются динамическими. Когда вызовы выполняются для объекта, точный тип которого известен (потому что объект не является указателем или ссылкой, или потому что его тип может быть статически выведен), тогда вызовы являются просто обычными вызовами функций-членов. Это не только означает, что нет накладных расходов на диспетчеризацию, но также и то, что эти вызовы могут быть встроены так же, как обычные вызовы.
Другими словами, ваш компилятор C ++ может работать, когда виртуальные функции не требуют виртуальной диспетчеризации, поэтому обычно нет причин беспокоиться об их производительности по сравнению с не виртуальными функциями.
Новое: Кроме того, мы не должны забывать общие библиотеки. Если вы используете класс, который находится в разделяемой библиотеке, вызов обычной функции-члена не будет просто хорошей последовательностью инструкций, как
callq 0x4007aa
. Он должен пройти через несколько циклов, например, через "таблицу ссылок на программы" или какую-то подобную структуру. Следовательно, косвенное обращение к разделяемой библиотеке может несколько (если не полностью) выровнять разницу в стоимости между (действительно косвенным) виртуальным вызовом и прямым вызовом. Таким образом, рассуждения о компромиссах виртуальных функций должны принимать во внимание то, как строится программа: монолитно ли класс целевого объекта связан с программой, выполняющей вызов.источник
потому что виртуальный вызов эквивалентен
где с невиртуальной функцией компилятор может постоянно сгибать первую строку, это разыменование сложения и динамический вызов, преобразованный в просто статический вызов
это также позволяет встроить функцию (со всеми вытекающими последствиями оптимизации)
источник