假设我有一个向量长度计算函数,它具有一个附加的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);
}
现在,可以使用两种calcLength
参数来调用inc
:何时在编译时知道inc
,何时不知道。我想为calcLength
(如1)的常见编译时值提供一个优化的inc
版本。
所以,我会有这样的东西:
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
我的问题是,是否可能以某种方式保留原始接口,并根据{{1的类型(是否为编译时常数)自动插入Constant
/ Var
}}?
inc
注意:这是一个简单的示例。在我的实际问题中,我有几个类似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));
的函数,它们很大,我不希望编译器内联它们。
注2:我也接受不同的方法。基本上,我想有一个解决方案,可以满足以下要求:
calcLength
指定为1
,则会实例化一个特殊函数,并且代码很可能被矢量化inc
不是编译时常量,则调用通用函数答案 0 :(得分:2)
如果此处的目标只是优化而不是在编译时上下文中使用,则可以向编译器提示您的意图:
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);
}
}
From 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);
});
}
答案 1 :(得分:0)
C ++没有提供检测提供的函数参数是否为常量表达式的方法,因此您无法自动区分提供的文字和运行时值。
如果参数必须是一个函数参数,并且您不愿意在两种情况下更改其调用方式,那么这里唯一的杠杆就是参数的类型:在这方面,您对Constant<1>()
和Var(inc)
的建议非常好。
答案 2 :(得分:0)
编译器可以在不费吹灰之力的情况下完成您想做的事情吗(嗯,您需要启用优化的构建,但这不用多说)。
编译器可以创建所谓的“函数克隆”,它们可以执行您想要的操作。克隆函数是用于常量传播的函数的副本,也就是使用常量参数调用的函数的结果汇编。我很少找到有关此功能的文档,因此如果您要依靠它,完全取决于您。
编译器可以完全内联该函数,可能使您的问题不成问题(您可以通过使用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
您的版本还可以。