[英]Function optimized for compile-time constant
假设我有一个向量长度计算函数,它有一个额外的inc
参数(它告诉相邻元素之间的距离)。 一个简单的实现是:
float calcLength(const float *v, int size, int inc) {
float l = 0;
for (int i=0; i<size*inc; i += inc) {
l += v[i]*v[i];
}
return sqrt(l);
}
现在,可以使用两种inc
参数调用calcLength
:在编译时知道inc
时和不知道时。 我想要一个优化的calcLength
版本,用于inc
常见编译calcLength
(如 1)。
所以,我会有这样的事情:
template <int C>
struct Constant {
static constexpr int value() {
return C;
}
};
struct Var {
int v;
constexpr Var(int p_v) : v(p_v) { }
constexpr int value() const {
return v;
}
};
template <typename INC>
float calcLength(const float *v, int size, INC inc) {
float l = 0;
for (int i=0; i<size*inc.value(); i += inc.value()) {
l += v[i]*v[i];
}
return sqrt(l);
}
}
所以,这可以使用:
calcLength(v, size, Constant<1>()); // inc is a compile-time constant 1 here, calcLength can be vectorized
或者
int inc = <some_value>;
calcLength(v, size, Var(inc)); // inc is a non-compile-time constant here, less possibilities of compiler optimization
我的问题是,是否有可能以某种方式保留原始接口,并根据inc
的类型(编译时常量与否)自动放入Constant
/ Var
?
calcLength(v, size, 1); // this should end up calcLength(v, size, Constant<1>());
calcLength(v, size, inc); // this should end up calcLength(v, size, Var(int));
注意:这是一个简单的例子。 在我的实际问题中,我有几个函数,比如calcLength
,而且它们很大,我不希望编译器内联它们。
注2:我也对不同的方法持开放态度。 基本上,我想有一个解决方案,它满足这些:
1
指定为inc
,则实例化一个特殊函数,并且代码很可能会被矢量化inc
不是编译时常量,则调用通用函数如果这里的目标只是优化,而不是在编译时上下文中启用使用,您可以向编译器提供有关您的意图的提示:
static float calcLength_inner(const float *v, int size, int inc) {
float l = 0;
for (int i=0; i<size*inc; i += inc) {
l += v[i]*v[i];
}
return sqrt(l);
}
float calcLength(const float *v, int size, int inc) {
if (inc == 1) {
return calcLength_inner(v, size, inc); // compiler knows inc == 1 here, and will optimize
}
else {
return calcLength_inner(v, size, inc);
}
}
从 Godbolt ,你可以看到calcLength_inner
已经被实例化了两次,有和没有常量传播。
这是一个 C 技巧(并且在 numpy 中广泛使用),但是您可以编写一个简单的包装器以使其更易于在 C++ 中使用:
// give the compiler a hint that it can optimize `f` with knowledge of `cond`
template<typename Func>
auto optimize_for(bool cond, Func&& f) {
if (cond) {
return std::forward<Func>(f)();
}
else {
return std::forward<Func>(f)();
}
}
float calcLength(const float *v, int size, int inc) {
return optimize_for(inc == 1, [&]{
float l = 0;
for (int i=0; i<size*inc; i += inc) {
l += v[i]*v[i];
}
return sqrt(l);
});
}
C ++没有提供检测提供的函数参数是否是常量表达式的方法,因此您无法自动区分提供的文字和运行时值。
如果参数必须是一个函数参数,并且你不愿意改变它在两种情况下调用的方式,那么你在这里唯一的杠杆就是参数的类型:你对Constant<1>()
建议Constant<1>()
vs Var(inc)
在这方面相当不错。
编译器可以做你想做的事吗?
编译器可以创建所谓的“函数克隆”,它可以执行您想要的操作。 克隆函数是用于常量传播的函数的副本,也就是使用常量参数调用的函数的结果程序集。 我发现关于此功能的文档很少,因此是否要依赖它取决于您。
编译器可以完全内联此函数,这可能会使您的问题成为非问题(您可以通过在标头中内联定义它、使用 lto 和/或使用编译器特定属性(如__attribute__((always_inline))
)来帮助它)
现在,我不是在鼓吹让编译器完成它的工作。 尽管这些时候编译器的优化非常出色,并且经验法则是信任优化器,但在某些情况下您需要手动干预。 我只是说要意识到并考虑到它。 哦,和往常一样衡量,衡量,衡量性能时,不要使用“我觉得我需要优化”的直觉。
float calcLength(const float *v, int size, int inc) {
float l = 0;
for (int i=0; i<size*inc; i += inc) {
l += v[i]*v[i];
}
return sqrt(l);
}
template <int Inc>
float calcLength(const float *v, int size) {
float l = 0;
for (int i=0; i<size*inc; i += inc) {
l += v[i]*v[i];
}
return sqrt(l);
}
这里的缺点是代码重复,ofc。 在调用站点也很少需要注意:
calcLength(v, size, inc); // ok
calcLength<1>(v, size); // ok
calcLength(v, size, 1); // nope
你的版本没问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.