为什么std::vector::operator[]比std::vector::at()快5到10倍?

42
在程序优化过程中,我尝试优化一个遍历向量的循环时,发现以下事实:::std::vector::at() 比 operator[] EXTREMELY慢!
在发布和调试版本(VS2008 x86)中,operator[] 比 at() 快5到10倍。
在网上阅读了一些资料后,我意识到 at() 进行了边界检查。好吧,但是,这会使操作减慢多达10倍吗?
这是什么原因?我的意思是,边界检查只是简单的数字比较,还是我漏掉了什么?
问题是造成性能下降的真正原因是什么?
此外,有没有办法使它更快?
我肯定会在其他代码部分(其中已经进行了自定义边界检查!)中将所有的 at() 调用与 [] 交换。
概念证明:
#define _WIN32_WINNT 0x0400
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <conio.h>

#include <vector>

#define ELEMENTS_IN_VECTOR  1000000

int main()
{
    __int64 freq, start, end, diff_Result;
    if(!::QueryPerformanceFrequency((LARGE_INTEGER*)&freq))
        throw "Not supported!";
    freq /= 1000000; // microseconds!

    ::std::vector<int> vec;
    vec.reserve(ELEMENTS_IN_VECTOR);
    for(int i = 0; i < ELEMENTS_IN_VECTOR; i++)
        vec.push_back(i);

    int xyz = 0;

    printf("Press any key to start!");
    _getch();
    printf(" Running speed test..\n");

    { // at()
        ::QueryPerformanceCounter((LARGE_INTEGER*)&start);
        for(int i = 0; i < ELEMENTS_IN_VECTOR; i++)
            xyz += vec.at(i);
        ::QueryPerformanceCounter((LARGE_INTEGER*)&end);
        diff_Result = (end - start) / freq;
    }
    printf("Result\t\t: %u\n\n", diff_Result);

    printf("Press any key to start!");
    _getch();
    printf(" Running speed test..\n");

    { // operator []
        ::QueryPerformanceCounter((LARGE_INTEGER*)&start);
        for(int i = 0; i < ELEMENTS_IN_VECTOR; i++)
            xyz -= vec[i];
        ::QueryPerformanceCounter((LARGE_INTEGER*)&end);
        diff_Result = (end - start) / freq;
    }

    printf("Result\t\t: %u\n", diff_Result);
    _getch();
    return xyz;
}

编辑:
现在该值被赋给了“xyz”,因此编译器不会将其“擦除”。


也许你应该对这些元素做一些实际的操作,而不仅仅是请求它们,否则编译器可能会将其优化掉。 - schnaader
1
在for循环中尝试像test_int += vec[i]这样做一些事情。由于您没有对向量元素进行任何操作,编译器可能会完全优化掉它。此外,还可以参考Ben的答案。 - schnaader
2
请注意,即使您进行了更改,优化器仍将获胜:它可以看到您从未使用计算出的“xyz”的值,因此该操作仍将被优化。您可以通过返回“xyz”(它无法优化“main()”的返回)来防止这种情况发生。 - James McNellis
1
@Poni:即使您更新了代码,我下面提出的观点仍然成立:当我运行此程序时,我没有看到两个循环之间有任何显着的性能差异。也许您可以提供一些您所看到的测试结果。 - James McNellis
@IvanaGajic - 请查看关于 at 的问题。 - jww
显示剩余3条评论
3个回答

59

原因是未经检查的访问可能只需一个处理器指令就能完成。而经过检查的访问还需要从内存中加载大小,将其与索引进行比较,并且(假设它在范围内)跳过一个有条件的分支到错误处理程序。还可能需要更多操作来处理可能抛出异常的情况。这样做会慢很多倍,这正是你拥有这两个选项的原因。

如果您可以证明索引在运行时检查之前已经在范围内,则使用operator[]。否则,请使用at()或在访问之前添加自己的检查。 operator[] 应该尽可能快,但如果索引无效,它将会产生严重的故障。


32
好的,如果你幸运的话,它会爆炸。 :) - Billy ONeal

32

我在我的机器上运行了你的测试代码:

在未优化的调试版本中,两个循环之间的差别微不足道。

在经过优化的发布版本中,第二个for循环被完全优化掉了(对 operator[] 的调用可能被内联,并且优化器可以看到循环没有实际作用并删除整个循环)。

如果我将循环体改为执行一些实际工作,例如 vec.at(i)++;vec[i]++;,则两个循环之间的差异微不足道。

我没有看到你所看到的五到十倍的性能差异。


他正在运行带有迭代器调试功能的调试版本。 - Hans Passant
@Hans:我的测试是使用默认设置进行的,因此在调试构建中启用了迭代器调试(但我认为这是预期结果——它们大致相等——因为它为op[]启用了边界检查)。如果我禁用迭代器调试,op[]at()快大约两倍。 (当我最初发布答案时,我并没有真正关注调试构建性能,但你是对的:迭代器调试可以在调试构建中产生很大的差异)。 - James McNellis

4

你没有对返回值做任何操作,因此如果编译器内联这些函数,它可以完全优化掉它们。或者它可以完全优化掉下标([])版本。从性能测量的角度来看,不进行优化是毫无意义的,你需要一些简单但有用的程序来使用这些函数,以便它们不会被优化掉。例如,你可以打乱向量(随机交换50000对元素)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接