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

Есть ли ограничение производительности во время выполнения при использовании интерфейсов (абстрактных базовых классов) в С++?

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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>();
}

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

Ответ 14

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

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

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

Ответ 15

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

Ответ 16

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