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)
是我想要根据 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
执行的操作,而性能成本可以忽略不计?
注意:这里的代码重复是不可取的,因为我正在处理的实际代码要复杂得多,即它可以对无序面板数据等执行迭代差异,其中 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 捕获,允许去掉一个参数。
我正在开发一些高性能的统计函数,并试图让它们根据一些函数参数执行不同的操作。目前的问题是开发一种通用代码,可以根据函数参数灵活地执行差分、部分/广义差分、增长率和对数差分。举一个基本的例子:
// 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
执行的操作,而性能成本可以忽略不计?
注意:这里的代码重复是不可取的,因为我正在处理的实际代码要复杂得多,即它可以对无序面板数据等执行迭代差异,其中 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 捕获,允许去掉一个参数。