我在以下代码中遇到了不同编译器的优化行为不一致问题:
class tester
{
public:
tester(int* arr_, int sz_)
: arr(arr_), sz(sz_)
{}
int doadd()
{
sm = 0;
for (int n = 0; n < 1000; ++n)
{
for (int i = 0; i < sz; ++i)
{
sm += arr[i];
}
}
return sm;
}
protected:
int* arr;
int sz;
int sm;
};
doadd
函数模拟了对成员变量的一些密集访问(对于这个问题忽略加法中的溢出)。与作为函数实现的相似代码相比:
int arradd(int* arr, int sz)
{
int sm = 0;
for (int n = 0; n < 1000; ++n)
{
for (int i = 0; i < sz; ++i)
{
sm += arr[i];
}
}
return sm;
}
doadd
方法在使用 Visual C++ 2008 的 Release 模式编译时运行速度比 arradd
函数慢大约1.5倍。当我将 doadd
方法修改如下(使用本地变量别名所有成员):
int doadd()
{
int mysm = 0;
int* myarr = arr;
int mysz = sz;
for (int n = 0; n < 1000; ++n)
{
for (int i = 0; i < mysz; ++i)
{
mysm += myarr[i];
}
}
sm = mysm;
return sm;
}
运行时大致相同,我可以得出结论:这是 Visual C++ 编译器缺少的优化吗?g++
在使用 -O2
或 -O3
编译时似乎做得更好,同时以相同的速度运行成员函数和普通函数。
基准测试是通过对一些足够大的数组(大小为数百万个整数)调用 doadd
成员和 arradd
函数来完成的。
编辑:一些细粒度的测试表明,主要问题在于 sm
成员。将所有其他成员替换为本地版本仍然使运行时长,但是一旦我将 sm
替换为 mysm
,运行时就变得等于函数版本。
解决方案
对于这段代码,我对答案感到失望(抱歉各位),因此我摆脱了懒惰并深入研究了反汇编列表。我的下面的答案总结了发现。简而言之:这与别名无关,而与循环展开有关,以及MSVC在决定展开哪个循环时应用的一些奇怪启发式。
tester
实例是在堆上分配的吗? - ereOnmain
函数的堆栈中。 - Eli Bendersky