Какова стоимость производительности наличия виртуального метода в классе C++?

Условие wp_merchant_meta.meta_key = 'last_login' является условием объединения таблиц и должно быть в операторе JOIN

SELECT 
    wp_merchants.*,
    wp_merchant_meta.meta_value 
FROM wp_merchants 
LEFT JOIN wp_merchant_meta ON wp_merchant_meta.merchant_id = wp_merchants.id 
    AND wp_merchant_meta.meta_key = 'last_login'
.
102
задан tshepang 8 May 2014 в 19:47
поделиться

8 ответов

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

Так, не действительно стоящий волнения о стоимости, если функция не что-то как тривиальное, Получают () / Набор () средство доступа, в котором что-либо кроме встроенного довольно расточительно. 7 нс наверху на функции, которая встраивает к 0,5 нс, серьезны; 7 нс наверху на функции, которая берет 500 мс для выполнения, бессмысленны.

большая стоимость виртуальных функций не является действительно поиском указателя функции в vtable (это - обычно просто единственный цикл), но что косвенный переход обычно не может предсказываться ответвлением. Это может вызвать большой конвейерный пузырь, поскольку процессор не может выбрать инструкции, пока косвенный переход (вызов через указатель функции) не удалился и новый вычисленный указатель команд. Так, стоимость вызова виртуальной функции намного больше, чем это могло бы казаться от рассмотрения блока..., но все еще только 7 наносекунд.

Редактирование: Andrew, Не Несомненно, и другие также поднимает очень положительный вопрос, что вызов виртуальной функции может вызвать неудачное обращение в кэш инструкции: если Вы переходите к адресу кода, который не находится в кэше тогда, целая программа приходит к глубокому зависанию, в то время как инструкции выбираются от оперативной памяти. Это всегда значительный останов: на Ксеноне, приблизительно 650 циклов (моими тестами).

Однако это не проблема, характерная для виртуальных функций, потому что даже прямой вызов функции вызовет мисс, если Вы перейдете к инструкциям, которые не находятся в кэше. То, что имеет значение, - была ли функция выполнена прежде недавно (делающий его более вероятно, чтобы быть в кэше), и может ли Ваша архитектура предсказать статичный (не виртуальный) ответвления и выбрать те инструкции в кэш заранее. Моя PPC не делает, но возможно новые аппаратные средства Intel делают.

Мои синхронизации управляют для влияния icache промахами на выполнении (сознательно, так как я пытался исследовать конвейер ЦП в изоляции), таким образом, они обесценивают ту стоимость.

94
ответ дан Crashworks 24 November 2019 в 04:34
поделиться

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

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

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

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

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

18
ответ дан Andrew Grant 24 November 2019 в 04:34
поделиться

Это зависит.:) (Вы ожидали что-либо еще?)

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

станд.:: копия () на простых типах POD может обратиться к простой memcpy стандартной программе, но типы не-POD должны быть обработаны более тщательно.

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

В худшем случае, можно видеть 5x более медленное выполнение (что число взято из университетского проекта, который я сделал недавно для перереализации нескольких стандартных классов библиотеки. Наш контейнер брал примерно 5x как долго для построения, как только тип данных, который он сохранил, получил vtable)

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

Однако производительность не должна быть Вашим основным вниманием здесь. Создание всего виртуального не является идеальным решением по другим причинам.

Разрешение всего быть переопределенным в производных классах делает его намного тяжелее для поддержания инвариантов класса. Как класс гарантирует, что это остается в согласованном состоянии, когда кто-либо из его методов мог быть переопределен когда-либо?

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

9
ответ дан jalf 24 November 2019 в 04:34
поделиться

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

Однако загромождение себя с издержками, если Вы не делаете needx, он возможно немного заходит слишком далеко. И большая часть classesare, не разработанный, чтобы быть наследованной от - для создания хорошего базового класса, требует больше, чем создание его виртуальных функций.

7
ответ дан 24 November 2019 в 04:34
поделиться

Дополнительные расходы - фактически ничто в большинстве сценариев. (простите игру слов). ejac уже отправил разумные относительные меры.

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

<час>

Относительно оптимизации:
важно знать и рассмотреть относительную стоимость конструкций Вашего языка. Большая нотация O является onl половиной истории - , как Ваше приложение масштабируется . Другая половина является постоянным множителем перед ним.

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

<час>

Изобретенный Пример: пустой виртуальный деструктор на массиве одного миллиона маленьких элементов может пахать по крайней мере через 4 МБ данных, перегружая Ваш кэш. Если тот деструктор может быть встроен далеко, данные не будут затронуты.

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

3
ответ дан peterchen 24 November 2019 в 04:34
поделиться

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

Рассматривают этот код, каков вывод?

#include <stdio.h>

class A
{
public:
    void Foo()
    {
        printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
        printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

Ничто удивляющее здесь:

A::Foo()
B::Foo()
A::Foo()

, Поскольку ничто не является виртуальным. Если виртуальное ключевое слово добавляется к передней стороне Foo и в A и в классах B, мы получаем это для вывода:

A::Foo()
B::Foo()
B::Foo()

В значительной степени, что все ожидают.

Теперь, Вы упомянули, что существуют ошибки, потому что кто-то забыл добавлять виртуальное ключевое слово. Поэтому рассмотрите этот код (где виртуальное ключевое слово добавляется к A, но не B класс). Каков вывод тогда?

#include <stdio.h>

class A
{
public:
    virtual void Foo()
    {
        printf("A::Foo()\n");
    }
};

class B : public A
{
public:
    void Foo()
    {
        printf("B::Foo()\n");
    }
};

int main(int argc, char** argv)
{    
    A* a = new A();
    a->Foo();

    B* b = new B();
    b->Foo();

    A* a2 = new B();
    a2->Foo();

    return 0;
}

Ответ: то же, как будто виртуальное ключевое слово добавляется к B? Причина состоит в том что подпись для B:: Foo соответствует точно как A:: Foo () и потому что Foo A является виртуальным, B - также.

Теперь рассматривают случай, где Foo B является виртуальным, и A не. Каков вывод тогда? В этом случае вывод

A::Foo()
B::Foo()
A::Foo()

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

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

Поэтому, если у Вас есть правило удалить виртуальное ключевое слово, оно не может иметь намеченного эффекта.

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

2
ответ дан Tommy Hui 24 November 2019 в 04:34
поделиться

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

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

1
ответ дан Dan Olson 24 November 2019 в 04:34
поделиться

Это потребует просто нескольким дополнительным asm инструкциям назвать виртуальный метод.

, Но я не думаю, что Вы волнуетесь, что забава (интервал a, интервал b) имеет несколько дополнительных инструкций 'по нажатию' по сравнению с забавой (). Не волнуйтесь о virtuals также, пока Вы не находитесь в специальной ситуации и видите, что это действительно приводит к проблемам.

P.S., Если у Вас есть виртуальный метод, удостоверьтесь, что у Вас есть виртуальный деструктор. Таким образом, Вы избежите возможных проблем

<час>

В ответ на комментарии 'Tom' и 'xtofl'. Я сделал маленькие тесты с 3 функциями:

  1. Виртуальный
  2. Нормальный
  3. Нормальный с 3 международными параметрами

Мой тест был простым повторением:

for(int it = 0; it < 100000000; it ++) {
    test.Method();
}

И здесь результаты:

  1. 3 913 секунд
  2. 3 873 секунды
  3. 3 970 секунд

Это было скомпилировано VC ++ в режиме отладки. Я сделал только 5 тестов на метод и вычислил среднее значение (таким образом, результаты могут быть довольно неточными)... Любым путем значения являются почти равным принятием 100 миллионов вызовов. И метод с 3 дополнительными нажатиями/поп был медленнее.

основной момент - то, что, если Вам не нравится аналогия с нажатием/поп, думайте дополнительный если/еще в Вашем коде? Сделайте Вы думаете о конвейере ЦП, когда Вы добавляете дополнительный если/еще;-) кроме того, Вы никогда не знаете, на каком ЦП будет работать код... Обычный компилятор может генерировать код, более оптимальный для одного ЦП и менее оптимальный для другого ( Компилятор C++ Intel )

-1
ответ дан alex2k8 24 November 2019 в 04:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: