Условные операции внутри функций C ++ без потери скорости или дублирования кода: использовать макросы, встроенные функции, шаблоны или иное? - PullRequest
2 голосов
/ 18 апреля 2020

Я разрабатываю некоторые высокопроизводительные статистические функции и пытаюсь заставить их выполнять различные операции на основе аргументов некоторых функций. Настоящая проблема заключается в разработке общего кода, который гибко выполняет различие, частичное / обобщенное различие, темпы роста и лог-различие на основе аргументов функции. Чтобы дать базовый c пример:

// General code to first-difference a vector:
std::vector<double> diff(std::vector<double> x, int ret = 1, double rho = 1, ...) { // ... = more arguments               int l = x.size();
                         std::vector<double> res(l);
                         for (int i = 1; i < l; ++i) res[i] = FUN(x[i], x[i - 1]); 
                         // rest of code ...
}

Теперь FUN(y, x) - это то, что я хочу эффективно варьировать, основываясь на аргументах ret и rho. Для простых различий это y-x, для обобщенных различий - y-rho*x, для логарифмических разностей - log(y/x), для темпов роста (y-x)*(100/x), и могут быть добавлены дополнительные параметры. Код применяется к большим наборам данных и должен быть быстрым, поэтому оптимально я бы использовал что-то вроде условно созданного макроса для FUN, то есть что-то вроде:

std::vector<double> diff(std::vector<double> x, int ret = 1, double rho = 1, ...) {
                         int l = x.size();
                         std::vector<double> res(l);
                         #define FUN(y, x) (ret==1 && rho==1) ? ((y)-(x)) : \
                                           (ret==1) ? ((y)-rho*(x)) :      \
                                           (ret==2) ? (log((y)/(x))) :     \
                                           (ret==3) ? ((y)-(x))*(100/(x))                           
                         for (int i = 1; i < l; ++i) res[i] = FUN(x[i], x[i - 1]); 
}

Это работает, но, судя по уменьшенному Скорость кода мне кажется, что я не создаю макрос, а только один макрос, и каждый раз, когда вызывается FUN, все условия оцениваются для выполнения правильной операции. Я немного рассмотрел эти команды препроцессора (#if, #elif, #else, #endif, #define и #undef), но мне кажется, что вы не можете использовать их для условного создания макроса на основе аргументов функции. Вторым подходом может быть использование встроенных функций, например:

inline double do_diff(double y, double x, double r) {
  return y-x; 
}
inline double do_gdiff(double y, double x, double r) {
  return y-r*x;
}
inline double do_logdiff(double y, double x, double r) {
  return log(y/x); 
}
inline double do_growth(double y, double x, double r) {
  return (y-x)*(100/x); 
}

std::vector<double> diff(std::vector<double> x, int ret = 1, double rho = 1, ...) {
                         int l = x.size();
                         std::vector<double> res(l);
                         auto FUN = (ret==1 && rho==1) ? do_diff : 
                                    (ret==1) ? do_gdiff :       
                                    (ret==2) ? do_logdiff :      
                                    (ret==3) ? do_growth;
                         for (int i = 1; i < l; ++i) res[i] = FUN(x[i], x[i - 1], rho); 
}

Проблема здесь заключается только в том, что он уменьшает скорость кода примерно в 1,5 раза. Учитывая, что это действительно простые операции и этот код должен быть максимально быстрым, я бы предпочел этого избежать. Итак, мой вопрос: есть ли способ изменить операцию, выполняемую FUN, с минимальными затратами на производительность?

Примечание. Дублирование кода здесь нежелательно, так как фактический код, над которым я работаю, гораздо сложнее, то есть он может выполнять многократные различия неупорядоченных данных панели и т. Д. c, около 700 строк, в которых FUN входит в нескольких местах.

Ответы [ 2 ]

3 голосов
/ 18 апреля 2020

Шаблон кажется более подходящим:

template <typename F>
std::vector<double> diff(std::vector<double> x, F f, double rho = 1, ...) {
     int l = x.size();
     std::vector<double> res(l);
     for (int i = 1; i < l; ++i) res[i] = f(x[i], x[i - 1], rho); 
     // ...
}

std::vector<double> diff(const std::vector<double>& x, ret = 1, double rho = 1, ...) {
    switch (ret)
    {
        case 1: return (rho == 1) ?
                       diff(x, []{double y, double x, double r) { return y-x; }, rho) :
                       diff(x, []{double y, double x, double r) { return y-r*x; }, rho);
        case 2: return diff(x, []{double y, double x, double r) { return log(y/x); }, rho);
        case 3: return diff(x, []{double y, double x, double r) { return (y-x)*(100/x); }, rho);
    }
    throw std::runtime_error("Invalid argument");
}

Кажется даже, что rho может быть захвачен только (одной) лямбдой, что позволяет избавиться от одного параметра.

2 голосов
/ 18 апреля 2020

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

Затем в вашем коде есть некоторые моменты, которые выделяются:

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

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

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

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

...