Каковы последствия производительности маркировки методов / свойств как виртуальных? - PullRequest
62 голосов
/ 10 февраля 2009

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

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

Ответы [ 7 ]

136 голосов
/ 10 февраля 2009

Виртуальные функции имеют очень незначительное снижение производительности по сравнению с прямыми вызовами. На низком уровне вы в основном смотрите на поиск массива, чтобы получить указатель на функцию, а затем вызов через указатель на функцию. Современные процессоры могут даже достаточно хорошо прогнозировать косвенные вызовы функций в своих предикторах ветвления, поэтому они, как правило, не будут слишком вредить современным конвейерам ЦП. На уровне сборки вызов виртуальной функции переводится в нечто вроде следующего, где 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, если виртуальная машина понимает, что они всегда идут по одному и тому же адресу.) Помимо ускорения, которое вы получаете благодаря самому встраиванию, встраивание обеспечивает несколько других оптимизаций, таких как постоянное свертывание, потому что вызывающий может знать, как вызываемый работает внутри. Для функций, которые достаточно велики, чтобы их нельзя было встроить, снижение производительности, скорее всего, будет незначительным. Для очень маленьких функций, которые могут быть встроены, вам нужно быть осторожным с виртуальными функциями.

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

15 голосов
/ 10 февраля 2009

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

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

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

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

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

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

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

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

11 голосов
/ 10 февраля 2009

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

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

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

4 голосов
/ 10 февраля 2009

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

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

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

  1. Сохранение регистров, генерация пролога / эпилога функции для настройки аргументов, копирование возвращаемого значения и т. П.
  2. переход на фиксированный и статически известный адрес

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

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

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

3 голосов
/ 10 февраля 2009

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

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

2 голосов
/ 10 февраля 2009

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

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

0 голосов
/ 10 февраля 2009

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

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

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

Грубо говоря, это иерархия производительности вызовов методов:

Не виртуальные методы <Виртуальные методы <Методы интерфейса (для классов) <Отправка делегата <MethodInfo.Invoke <Type.InvokeMember </p>

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...