использование виртуальных функций по сравнению со static_cast из базы в производную - PullRequest
1 голос
/ 29 ноября 2010

Я пытаюсь понять, какая реализация ниже "быстрее". Предположим, что этот код компилируется с флагом -DVIRTUAL и без него.

Я предполагаю, что компиляция без -DVIRTUAL будет быстрее, потому что:

a] vtable не используется

b] Компилятор может оптимизировать инструкции по сборке, потому что он «точно» знает, какой вызов будет сделан с учетом различных опций (существует только конечное число опций).

Мой вопрос ТОЛЬКО связан со скоростью, а не с красивым кодом.

a] Я прав в своем анализе выше?

b] Будет ли комбинация предиктор ветки / компилятор достаточно разумной, чтобы оптимизировать ее для данной ветви оператора switch? Смотрите, что "тип" является const int.

c] Есть ли другие факторы, которые я упускаю?

Спасибо!

#include <iostream>

class Base
{
public:
    Base(int t) : type(t) {}
    ~Base() {}

   const int type;
#ifdef VIRTUAL
    virtual void fn1()=0;
#else
    void fn2();
#endif
};

class Derived1 : public Base
{
public:
    Derived1() : Base(1) { }
    ~Derived1() {}
    void fn1() { std::cout << "in Derived1()" << std::endl; }
};

class Derived2 : public  Base
{
public:
    Derived2() : Base(2) {  }
    ~Derived2() { }
    void fn1() { std::cout << "in Derived2()" << std::endl; }
};


#ifndef VIRTUAL
    void Base::fn2()
    {
        switch(type)
        {
        case 1:
            (static_cast<Derived1* const>(this))->fn1();
            break;
        case 2:
            (static_cast<Derived2* const>(this))->fn1();
            break;
        default:
            break;
        };
    }
#endif


int main()
{
    Base *test = new Derived1();
#ifdef VIRTUAL
    test->fn1();
#else
test->fn2();
#endif
    return 0;
}

Ответы [ 7 ]

1 голос
/ 29 ноября 2010

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

1 голос
/ 29 ноября 2010

Невозможно ответить без указания параметров компилятора и компилятора.

Я не вижу особой причины, по которой ваш не виртуальный код обязательно должен быть быстрее, чем виртуальный код. На самом деле, коммутатор может быть медленнее, чем vtable, так как вызов, использующий vtable, загрузит адрес и перейдет к нему, тогда как коммутатор загрузит целое число и немного подумает. Любой из них может быть быстрее. По очевидным причинам виртуальный вызов не определен стандартом как «медленнее, чем любая другая вещь, которую вы придумаете, чтобы заменить его».

Я думаю, что весьма маловероятно, что случайно выбранный компилятор фактически встроит вызов в виртуальном случае, но ему, безусловно, разрешено (в соответствии с правилом «как будто»), поскольку динамический тип *test может быть определен анализ потока данных или аналогичный. Я думаю, что вполне вероятно, что при включенной оптимизации случайно выбранный компилятор встроит все в не виртуальный случай. Но затем вы привели небольшой пример с очень короткими функциями в одном TU, поэтому встраивание особенно легко.

1 голос
/ 29 ноября 2010

Вы измерили производительность, чтобы увидеть, есть ли вообще разница?

Полагаю, нет, потому что тогда вы не спросите здесь. Это единственный разумный ответ.

1 голос
/ 29 ноября 2010

Я думаю, вы неправильно поняли VTable. VTable - это просто таблица переходов (в большинстве реализаций, хотя AFAIK спецификация не гарантирует этого!). На самом деле, вы можете сказать, что это гигантское заявление о переключении. Таким образом, я бы поспорил, что скорость будет одинаковой для обоих ваших методов.

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

0 голосов
/ 29 ноября 2010

Это зависит от платформы и компилятора.Оператор switch может быть реализован как тестовая ветвь или таблица переходов (т. Е. Косвенная ветвь).Функция virtual обычно реализуется как косвенная ветвь.Если ваш компилятор превращает оператор switch в таблицу переходов, эти два подхода отличаются одним дополнительным разыменованием.Если это так, и это конкретное использование происходит достаточно редко (или кэш-памяти достаточно), то вы можете увидеть разницу из-за дополнительной ошибки в кэше.

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

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

0 голосов
/ 29 ноября 2010

Обратите внимание, что:

  • static_cast версия может вводить ветвь (скорее всего, нет, если она оптимизирована для таблицы переходов),
  • Версия vtable во всех известных мне реализациях приведет к созданию таблицы переходов.

См. Образец здесь?

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

0 голосов
/ 29 ноября 2010

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

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