Каковы последствия для производительности методов / свойств маркировки как виртуальных?


вопрос, как указано в заголовке: каковы последствия для производительности методов / свойств маркировки как виртуальных?

примечание - Я предполагаю, что виртуальные методы будут не быть перегружены в общем случае; я обычно буду работать с базовым классом здесь.

7 59

7 ответов:

виртуальные функции имеют только очень небольшие накладные расходы по сравнению с прямыми вызовами. На низком уровне вы в основном смотрите на поиск массива, чтобы получить указатель функции, а затем вызов через указатель функции. Современные процессоры могут даже предсказывать косвенные вызовы функций достаточно хорошо в своих предсказателях ветвей, поэтому они, как правило, не слишком сильно повредят современным процессорным конвейерам. На уровне сборки вызов виртуальной функции преобразуется в нечто вроде следующего, где I - это произвольное непосредственное значение.

MOV EAX, [EBP + I] ; Move pointer to class instance into register
MOV EBX, [EAX] ;  Move vtbl pointer into register.
CALL [EBX + I]  ;   Call function

Vs. следующее для прямого вызова функции:

CALL I  ;  Call function directly

реальные накладные расходы входит в том, что виртуальные функции не могут быть встроены, по большей части. (Они могут быть на языках JIT, если виртуальная машина понимает, что они всегда идут по одному и тому же адресу.) Помимо ускорения, которое вы получаете от самого встраивания, встраивание позволяет несколько других оптимизаций, таких как постоянное сворачивание, потому что вызывающий может знать, как вызываемый работает внутри. Для функций, которые достаточно велики, чтобы не быть встроенными в любом случае, хит производительности, вероятно, будет незначительным. Для очень маленьких функций, которые могут быть встроены, именно тогда вам нужно быть осторожным с виртуальными функциями.

Edit: еще одна вещь, чтобы иметь в виду, что все программы требуют управления потоком, и это никогда не бесплатно. Что бы заменить вашу виртуальную функцию? Заявление о переключении? Серию операторов if? Это все еще ветви, которые могут быть непредсказуемыми. Кроме того, учитывая ветвь N-way, ряд операторов if найдет правильный путь в O(N), а виртуальная функция найдет его в O(1). Оператор switch может быть O(N) или O(1) в зависимости от того, оптимизирован ли он для таблицы переходов.

Рико Мариани излагает вопросы, касающиеся производительности в его производительность лакомые кусочки блог, где он заявил:

Виртуальные Методы: вы используете виртуальные методы при прямых вызовах подойдет? Много раз люди идут с виртуальные методы для обеспечения будущего растяжимость. Расширяемость-это хорошая вещь, но она приходит по цене - убедитесь, что ваша полная расширяемость история проработана и что вы используете виртуальных функций на самом деле идет чтобы получить вас туда, где вы должны быть. Например, иногда люди думают: через проблемы с сайтом вызова, но затем не рассматривайте, как " расширенный" объекты будут созданы. Позже они понимают, что (большинство) виртуальные функции не помогли вообще а им нужен был совсем другой модель для получения "расширенных" объектов в систему.

запечатывания: Запечатывание может быть путем ограничение полиморфизма вашего класс только те сайты, где нужен полиморфизм. Если вы будете полностью контролировать то типа запечатывания может быть отличная вещь для производительности как это позволяет прямые звонки и встраивание.

в основном аргумент против виртуальных методов заключается в том, что он запрещает коду быть кандидатом в подкладку, в отличие от прямых вызовов.

в статье MSDN повышение производительности и масштабируемости приложений .NET, это еще пояснил:

рассмотрим компромиссы виртуальных членов

использование виртуальных членов для обеспечения расширяемости. Если вам не нужно расширять свой класс дизайн, избегайте виртуальных членов, потому что они дороже для вызова из-за виртуального поиск в таблице, и они побеждают определенные оптимизации производительности во время выполнения. Например, виртуальные члены не могут быть встроены компилятором. Кроме того, когда вы разрешаете подтипирование, вы фактически представляете очень сложный контракт с потребителями, и вы неизбежно в конечном итоге с проблемами управления версиями, когда вы пытаетесь обновить свой класс в будущем.

критика вышеизложенного, однако, исходит из лагеря TDD/BDD (который хочет, чтобы методы по умолчанию были виртуальными), утверждая, что влияние производительности в любом случае незначительно, тем более, что мы получаем доступ к гораздо более быстрым машинам.

обычно виртуальный метод просто проходит через одну таблицу указателей функций для достижения фактического метода. Это означает одно дополнительное разыменование и еще один круговой переход к памяти.

пока цена нет совершенно нул, она весьма минимальна. Если это помогает вашей программе вообще иметь виртуальные функции, непременно сделайте это.

гораздо лучше иметь хорошо продуманную программу с крошечным, крошечным, крошечным хитом производительности, а не неуклюжей программой просто ради избегая v-таблицы.

трудно сказать наверняка, потому что компилятор .NET JIT может оптимизировать накладные расходы в некоторых (многих?) случаи.

но если он не оптимизирует его, мы в основном говорим о дополнительном косвенном указателе.

то есть, когда вы вызываете невиртуальный метод, вы должны

  1. сохранить регистры, сгенерировать функцию prologue / epilogue для настройки аргументов, скопировать возвращаемое значение и тому подобное.
  2. перейти к фиксированному, и статически известно, адрес

1 одинаково в обоих случаях. Что касается 2, с виртуальным методом, вы должны вместо этого прочитать из фиксированного смещения в таблице vtable объекта, а затем перейти туда, где это указывает. Это затрудняет прогнозирование ветвей, и это может вытолкнуть некоторые данные из кэша ЦП. Таким образом, разница не огромна, но она может сложиться, если вы сделаете каждый вызов функции виртуальным.

Он также может препятствовать оптимизации. Компилятор может легко встроить вызов в a невиртуальная функция, потому что она точно знает, какая функция вызывается. С виртуальной функцией это немного сложнее. JIT-компилятор все еще может это сделать, как только он определит, какая функция вызывается, но это намного больше работы.

в целом, он все еще может добавить вверх, особенно в критических областях. Но это не то, о чем вам нужно беспокоиться, если функция не вызывается по крайней мере несколько сотен тысяч раз в секунду.

Я запустил этот тест в C++. Виртуальный вызов функции занимает (на 3 ГГц PowerPC) от 7-20 наносекунд дольше, чем прямой вызов функции. Это означает, что это действительно имеет значение только для функций, которые вы планируете звонить миллион раз в секунду, или для функций, которые настолько малы, что накладные расходы могут быть больше, чем сама функция. (Например, создание виртуальных функций доступа из слепой привычки, вероятно, неразумно.)

Я не запускал свой тест в C#, но я ожидаю что разница будет еще меньше, так как почти каждая операция в среде CLR включает в себя косвенное в любом случае.

из ваших тегов, вы говорите c#. Я могу ответить только с точки зрения Дельфи. Я думаю, что это будет похоже. (Я ожидаю отрицательной обратной связи здесь :))

статический метод будет связан во время компиляции. Виртуальный метод требует поиска во время выполнения, чтобы решить, какой метод вызывать, поэтому есть небольшие накладные расходы. Это важно только в том случае, если метод мал и вызывается часто.

на стороне рабочего стола не имеет значения, перегружен ли метод или нет, они подвергаются дополнительному уровню косвенности через таблицу указателей метода (виртуальная таблица методов), что означает, что примерно 2 дополнительных чтения памяти через косвенность перед вызовом метода сравнивают не виртуальные методы на не запечатанных классах и не окончательных методах.

[как интересный факт, на compact framework версии 1.0 перегрев больше, поскольку он не использует виртуальные таблицы методов, а просто отражение, чтобы найти правильный метод для выполнения при вызове виртуального метода.]

кроме того, виртуальные методы гораздо реже являются кандидатами для встраивания или других оптимизаций, таких как хвостовой вызов, чем не виртуальные методы.

примерно такова иерархия производительности вызовов методов:

не виртуальные методы

но ни один из этих последствия для производительности различных механизмов отправки не имеют значения, если вы не доказали это с помощью измерения ;) (и даже тогда архитектурные последствия, читаемость и т. д. могут иметь большой вес, на котором можно выбрать)