Снижение производительности при работе с интерфейсами на C++?

StackOverflow https://stackoverflow.com/questions/113830

Вопрос

Существует ли снижение производительности во время выполнения при использовании интерфейсов (абстрактных базовых классов) в C++?

Это было полезно?

Решение

Короткий ответ:Нет.

Длинный ответ:На скорость влияет не базовый класс или количество предков класса в его иерархии.Единственное, это стоимость вызова метода.

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

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

Другие советы

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

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

Стоимость виртуального звонка зависит от платформы

Что касается штрафа за накладные расходы на вызов по сравнению с обычным вызовом функции, ответ зависит от вашей целевой платформы.Если вы нацелены на ПК с процессором x86/x64, штраф за вызов виртуальной функции очень мал, поскольку современный процессор x86/x64 может выполнять прогнозирование ветвления при косвенных вызовах.Однако если вы ориентируетесь на PowerPC или какую-либо другую RISC-платформу, штраф за виртуальный вызов может быть весьма значительным, поскольку на некоторых платформах непрямые вызовы никогда не прогнозируются (см. Лучшие практики кроссплатформенной разработки для ПК/Xbox 360).

За вызов виртуальной функции взимается небольшой штраф по сравнению с обычным вызовом.Вы вряд ли заметите разницу, если только вы не выполняете сотни тысяч вызовов в секунду, но за дополнительную ясность кода в любом случае стоит заплатить цену.

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

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

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

class AbstractAlgo
{
    virtual int func();
};

class Algo1 : public AbstractAlgo
{
    virtual int func();
};

class Algo2 : public AbstractAlgo
{
    virtual int func();
};

void compute(AbstractAlgo* algo)
{
      // Use algo many times, paying virtual function cost each time

}   

int main()
{
    int which;
     AbstractAlgo* algo;

    // read which from config file
    if (which == 1)
       algo = new Algo1();
    else
       algo = new Algo2();
    compute(algo);
}

То же самое с использованием полиморфизма времени компиляции

class Algo1
{
    int func();
};

class Algo2
{
    int func();
};


template<class ALGO>  void compute()
{
    ALGO algo;
      // Use algo many times.  No virtual function cost, and func() may be inlined.
}   

int main()
{
    int which;
    // read which from config file
    if (which == 1)
       compute<Algo1>();
    else
       compute<Algo2>();
}

Я не думаю, что сравнение затрат происходит между вызовом виртуальной функции и прямым вызовом функции.Если вы думаете об использовании абстрактного базового класса (интерфейса), то у вас есть ситуация, когда вы хотите выполнить одно из нескольких действий на основе динамического типа объекта.Вам придется как-то сделать этот выбор.Один из вариантов — использовать виртуальные функции.Другой вариант — переключение типа объекта либо через RTTI (потенциально дорого), либо добавление метода type() в базовый класс (потенциально увеличивающее использование памяти каждым объектом).Таким образом, стоимость вызова виртуальной функции следует сравнивать со стоимостью альтернативы, а не со стоимостью бездействия.

Большинство людей отмечают штрафы за время выполнения, и это справедливо.

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

Ваш опыт может варьироваться и явно зависит от приложения, которое вы разрабатываете.

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

Обратите внимание, что множественное наследование приводит к раздуванию экземпляра объекта несколькими указателями на виртуальную таблицу.В G++ на платформе x86, если у вашего класса есть виртуальный метод и нет базового класса, у вас есть один указатель на vtable.Если у вас есть один базовый класс с виртуальными методами, у вас все равно будет один указатель на vtable.Если у вас есть два базовых класса с виртуальными методами, у вас есть два указатели на виртуальные таблицы в каждом экземпляре.

Таким образом, при множественном наследовании (именно это и есть реализация интерфейсов в C++) вы платите умножение размера указателя базовых классов на размер экземпляра объекта.Увеличение объема памяти может иметь косвенное влияние на производительность.

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

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

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

Однако это плохой повод испортить ваш дизайн.Если вам нужно больше производительности, используйте более быстрый сервер.

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

Да, но, насколько мне известно, ничего примечательного.Снижение производительности происходит из-за «косвенности», которая присутствует в каждом вызове метода.

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

Если вы хотите быть уверенным, вам следует провести собственные тесты.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top