Ускорение вызовов виртуальных функций в gcc - PullRequest
5 голосов
/ 01 апреля 2009

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

Какими способами я мог бы ускорить это, за исключением переписывания всего, чтобы не быть виртуальным?

Ответы [ 7 ]

9 голосов
/ 01 апреля 2009

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

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

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

1007 * Е.Г. *

class Foo
{
public:

inline void update( void )
{
  if (can_early_out)
    return;

  updateImpl();
}

protected:

virtual void updateImpl( void ) = 0;    
}; 
6 голосов
/ 01 апреля 2009

Если виртуальный вызов действительно является узким местом, попробуйте CRTP .

6 голосов
/ 01 апреля 2009

Тратится ли время на фактический вызов функции или на саму функцию?

Виртуальный вызов функции на заметно медленнее, чем не виртуальный вызов, поскольку виртуальный вызов требует дополнительной разыменования. (Google для 'vtable', если вы хотите прочитать все подробности.)) Обновление: Оказывается, статья в Википедии неплоха в этом.

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

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

5 голосов
/ 01 апреля 2009

Обратите внимание, что «virtual» и «inline» не являются противоположностями - метод может быть и тем, и другим. Компилятор будет рад встроить виртуальную функцию, если он может определить тип объекта во время компиляции:

struct B {
    virtual int f() { return 42; }
};

struct D : public B {
    virtual int f() { return 43; }
};

int main(int argc, char **argv) {
    B b;
    cout << b.f() << endl;   // This call will be inlined

    D d;
    cout << d.f() << endl;   // This call will be inlined

    B& rb = rand() ? b : d;
    cout << rb.f() << endl;  // Must use virtual dispatch (i.e. NOT inlined)
    return 0;
}

[ОБНОВЛЕНИЕ: Убедитесь, что истинный динамический тип объекта rb не может быть известен во время компиляции - благодаря MSalters]

Если тип объекта может быть определен во время компиляции, но функция не является встроенной (например, она велика или определяется вне определения класса), она будет вызываться не виртуально.

1 голос
/ 02 апреля 2009

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

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

1 голос
/ 02 апреля 2009

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

0 голосов
...