哪种编码风格更适合编译器优化?特别是,我对 1) 最小化立即丢弃的临时值的数量和 2) 自动矢量化(即生成用于算术的 SIMD 指令)感兴趣。
假设我有这个结构:
#define FOR_EACH for (int i = 0; i < N; ++i)
template<typename T, unsigned N>
struct Vector {
void scale(T scalar) {
FOR_EACH v[i] *= scalar;
}
void add(const Vector<T, N>& other) {
FOR_EACH v[i] += other.v[i];
}
void mul(const Vector<T, N>& other) {
FOR_EACH v[i] *= other.v[i];
}
T v[N];
};
此结构的示例用法:
Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
v1.scale(10);
v1.add(v2);
v1.mul(v2);
这是可变的方法。
另一种不可变的方法可能如下所示:
template<typename T, unsigned N>
struct Vector {
Vector(const Vector<T, N>& other) {
memcpy(v, other.v, sizeof(v));
}
Vector<T, N> operator+(const Vector<T, N>& other) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] += other.v[i];
return result;
}
Vector<T, N> operator*(T scalar) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] *= scalar;
return result;
}
Vector<T, N> operator*(const Vector<T, N>& other) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] *= other.v[i];
return result;
}
T v[N];
};
示例用法:
Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
auto result = (v1 * 10 + v2) * v2;
现在,我不关心这个问题中的 API 设计。假设这两种解决方案在这方面都是可行的。
此外,代替int
示例代码,它可以是float
或double
也可以。
我感兴趣的是:现代 C++ 编译器更容易分析哪种设计?我并不是特别针对任何单个编译器。如果您有任何编译器的经验并且知道它如何处理我所询问的优化,请分享您的经验。
第二个版本产生了很多临时值。如果编译器最终内联所有运算符调用并看到其中包含的所有算术表达式,编译器能否摆脱这些?(我假设没有内联没有编译器可以消除临时因为可能的副作用)
第一个版本最小化了临时变量的数量,但构建了严格的顺序计算。编译器是否仍然可以推断出意图并以最小化操作数量并允许并行化(在 CPU 指令级别)的方式对操作重新排序?
现代编译器对上述循环进行矢量化有多难?