【发布时间】:2017-07-25 22:24:00
【问题描述】:
下面声明的 C++ vector 类中的 norm 成员函数被标记为 const 并且(据我所知)不包含任何副作用。
template <unsigned int N>
struct vector {
double v[N];
double norm() const {
double ret = 0;
for (int i=0; i<N; ++i) {
ret += v[i]*v[i];
}
return ret;
}
};
double test(const vector<100>& x) {
return x.norm() + x.norm();
}
如果我在 gcc 编译器(5.4 版)和优化打开(即-O3)的情况下多次调用const 实例化vector(参见上面的test 函数)上的norm,那么编译器内联norm,但仍然多次计算norm 的结果,即使结果不应该改变。为什么编译器不优化对norm 的第二次调用并且只计算一次这个结果? This answer 似乎表明如果编译器确定 norm 函数没有任何副作用,则编译器应该执行此优化。为什么在这种情况下不会发生这种情况?
请注意,我正在确定编译器使用 Compiler Explorer 生成的内容,并且 gcc 5.4 版的程序集输出如下所示。 clang 编译器给出了类似的结果。另请注意,如果我使用 gcc 的编译器属性手动将 norm 标记为使用 __attribute__((const)) 的 const 函数,那么第二个调用会按照我的意愿进行优化,但我的问题是为什么 gcc(和 clang)不这样做自动因为norm 定义可用?
test(vector<100u>&):
pxor xmm2, xmm2
lea rdx, [rdi+800]
mov rax, rdi
.L2:
movsd xmm1, QWORD PTR [rax]
add rax, 8
cmp rdx, rax
mulsd xmm1, xmm1
addsd xmm2, xmm1
jne .L2
pxor xmm0, xmm0
.L3:
movsd xmm1, QWORD PTR [rdi]
add rdi, 8
cmp rdx, rdi
mulsd xmm1, xmm1
addsd xmm0, xmm1
jne .L3
addsd xmm0, xmm2
ret
【问题讨论】:
-
(const好像错了,我觉得pure更好,但是优化没有发生)这是优化顺序的问题。如果函数是 const,x+x 很早就优化为 2*x(x 的单次出现)。否则,如果您将该函数标记为 noinline,则第二次调用将被优化为冗余。但是如果首先发生内联,那么编译器就很难注意到两个循环都在计算相同的东西(循环融合看起来是朝那个方向迈出的潜在有用的一步)。
-
按值传递向量时会发生同样的事情吗?仅仅因为你有一个 const 引用并不意味着它没有在其他地方被修改。 const 引用只是一个信号,表明被调用者(而不是调用者)不会更改被引用的对象。国际海事组织。在一般情况下,GCC 优化第二次调用是错误的,因为您添加了 __attribute__((const)) 但它可能能够进行更复杂的分析并确定在有限的测试用例中可以省略第二次调用.
-
我猜想在不正确的互斥环境中,另一个函数可能会在调用
norm之间更改数组中的内容。 -
对我来说更有趣的是编译器正在使用 SSE 进行优化,但是一次乘以 1 (mulsd) 而不是一次乘以 2 (mulpd)。更多证据表明,如果您有时间要求严格的代码,请不要依赖编译器来做“正确的事情”。
-
@J.H.Bonarius:那将是未定义的行为。
标签: c++ gcc optimization clang++