C++ 函数内部的条件操作不会降低速度或代码重复:使用宏、内联函数、模板还是其他?

Conditional operations inside C++ functions without loss of speed or code duplication: use macros, inline functions, templates or otherwise?

我正在开发一些高性能的统计函数,并试图让它们根据一些函数参数执行不同的操作。目前的问题是开发一种通用代码,可以根据函数参数灵活地执行差分、部分/广义差分、增长率和对数差分。举一个基本的例子:

// 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) 是我想要根据 retrho 的参数有效变化的东西。对于简单差异,它是 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 执行的操作,而性能成本可以忽略不计?

注意:这里的代码重复是不可取的,因为我正在处理的实际代码要复杂得多,即它可以对无序面板数据等执行迭代差异,其中 FUN 进入大约 700 行在多个地方。

首先,宏只是对源代码执行的一种文本替换形式。任何你写成宏的东西,你也可以写成常规代码。查看任何 C++ 常见问题解答,它会告诉您宏是邪恶的,通常应避免使用它们。你的情况并不特殊,它不会从使用宏中受益。

那么,您的代码中有一些突出的地方:

  • 您正在按值将向量传递给函数。
  • 您正在从函数中按值返回向量。
  • 当现代 auto 可用时,使用 int 作为尺码。

这表明您仍然有一些基础知识需要学习 and/or 您正在以不同语言的风格写作。后者会导致非惯用代码,这反过来可能会使编译器难以优化。

最后,您的第二种方法为四个函数之一创建了一个别名,这可能归结为一个函数指针。这将不会被内联。其中一些函数的冗余参数是优化器的另一个障碍。

建议:创建四个单独的函数,它们接受一个向量并重写当前的 diff() 以根据您现有的逻辑分派给这些函数。此外,您确实有测量值,但请尝试 运行 在分析器中进行测量。它会更准确地告诉你时间都花在了哪里,这可能会让你大吃一惊。

模板似乎更合适:

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 甚至只能被(一个)lambda 捕获,允许去掉一个参数。