对于以下代码,我在使用不同的编译器时遇到不一致的优化行为:

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;
}

在Visual C++ 2008中以 Release模式进行编译时, doadd方法的运行速度比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,运行时间就等于函数版本。



解析度

失望的回答(抱歉),我摆脱了懒惰,沉迷于此代码的反汇编 list 。我的answer below总结了调查结果。简而言之:它与别名无关,与循环展开有关,并且与MSVC在决定展开哪个循环时适用一些奇怪的启发式方法。

最佳答案

这可能是一个别名问题-编译器无法知道sm永远不会指向实例变量arr,因此它必须将sm视为有效的 volatile ,并将其保存在每次迭代中。您可以将sm设为其他类型,以检验此假设。或者只是使用一个临时的本地和(将被缓存在寄存器中),然后将其分配到sm末尾。

关于c++ - 在C++中优化对成员的访问,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/3930813/

10-12 14:56