Вызывает ли функция (или виртуальная функция) дорогостоящую операцию в C ++? - PullRequest
1 голос
/ 03 марта 2012

Я разрабатываю приложение, основанное на математике (sin, cos, sqrt и т.д.).Эти функции выполняются некоторое время, но имеют высокую точность.

Некоторым моим клиентам не нужна такая высокая точность, но им нужно, чтобы они были максимально быстрыми.

Так что у меня естьмоя функция Sin, представляющая собой простой массив (который создается до запуска программы), который принимает градус от 0 до 360 и возвращает его sin (скажем, массив имеет 360 значений).

Iхочу создать интерфейс:

interface MyMath
{
    double PreciseSin(double x);
    double PreciseCos(double x);
}

Он будет унаследован

  1. "Precise math", реализация которого вызовет нормальную функцию sin, cos.
  2. «Быстрая математика», которая будет использовать трюк с массивом, который я объяснил ранее.

Мой код будет использовать переменную типа «mymath» для выполнения вычислений, и в начале он будет инициализирован с помощью PrecIMMath илиfastMath.

Наконец, у меня следующие вопросы:

  1. Сколько времени я буду платить за вызов виртуальной функции, которая вызывает "Math.sin" вместо прямого вызова?
  2. Сможет ли компилятор оптимизировать его и понять, что в случае, если я инициализирую MyMath с помощью PriciseMath, все, что мне нужно, это вызывать нормальные функции Sin и Cos?
  3. Могу ли я изменить свой дизайн, чтобы помочь компилятору понять и оптимизировать мой код?

Ответы [ 4 ]

4 голосов
/ 03 марта 2012

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

class PreciseMath{
    public:
    inline double sin(double sin){
        //code goes here
    }
    inline double cos(double sin){
        //code goes here
    }
    inline double sqrt(double sin){
        //code goes here
    }
};
class FastMath{
    public:
    inline double sin(double sin){
        //code goes here
    }
    inline double cos(double sin){
        //code goes here
    }
    inline double sqrt(double sin){
        //code goes here
    }
};
template<class T>
class ExpensiveOP{
    public:
    T math;
    void do(){
        double x = math.sin(9);
        x=math.cos(x);
        //etc
    }
}
ExpensiveOP<PreciseMath> preciseOp;
ExpensiveOP<FastMath> fasterOp;
2 голосов
/ 03 марта 2012

Для одного и двух:

  1. Вы будете платить ту же сумму, что и вызов функции через указатель на функцию.Это не очень много.

  2. Нет, компилятор не будет оптимизировать вызовы виртуальных функций в вызовы статических функций, потому что он не может знать, что тип не изменитсякак-то во время выполнения (например, получение указателя из какого-то внешнего кода, о котором он ничего не знает). Delnan сообщил мне в комментариях, что в очень простых случаях, таких как A* a = new A; a->func(), компилятор может видеть, что a будетникогда не будет ничем иным, кроме A, поэтому он может выполнять «девиртуализацию» и оптимизировать вызов виртуальной функции в вызов статической функции.Тем не менее, случаи, когда он может сделать это, довольно редки, и если вы получили указатель, скажем, от аргумента функции, он не сможет сделать это, потому что это может быть на самом деле производный тип.

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

1 голос
/ 03 марта 2012

Сколько штрафа я буду платить за вызов виртуальной функции, которая вызывает "Math.sin" вместо прямого вызова?

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

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

Сможет ли компилятор оптимизировать его и понять, что в случае, если я инициализирую MyMath с помощью PriciseMath, все, что мне нужно, это вызывать нормальные функции Sin и Cos?

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

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

Могу ли я изменить свой дизайн, чтобы помочь компилятору понять и оптимизировать мой код?

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

(Ответ RTS является хорошей иллюстрацией обоих этих методов.)


И, наконец, если производительность действительно важна для вас, не полагайтесь только на советы других людей (включая меня) - всегда выполняйте измерения самостоятельно!

0 голосов
/ 04 марта 2012

Если вы можете предоставить различные двоичные файлы, просто выполните условную компиляцию :

  namespace MyMath {
#ifdef FAST_MATH
    double sin(double x) { /* do it fast */ }
    double sos(double x) { /* do it fast */ }
#else
    double sin(double x) { /* do it precise */ }
    double sos(double x) { /* do it precise */ }
#endif
  }

А затем вызовите ваш компилятор с -DFAST_MATH для генерации быстрого двоичного файла и без него для точного двоичного файла.

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