就百分比而言,这两种方法之间的平均性能差异有多大?

Percentage-wise, how much difference in performance is there, on average, between these 2 methods?

我在尝试理解标准库的实现时有一个问题。

给定

std::vector<int> myVec = { ... }; // some vector of ints

以下哪项平均速度更快,快多少,为什么?

(1)

for (int i(0), j(myVec.size()); i < j; ++i) std::cout << myVec[i];

(2)

for (std::vector<int>::iterator i(myVec.begin()), j(myVec.end()); i != j; ++i) std::cout << *it;

第二种情况更快。

当你增加迭代器时,你只是增加了指针。

访问具有索引的元素包括索引乘以类型的大小,以计算指针。

好吧让我们看看...

第一个循环:调用size成员函数,在std::vector对象上调用N次operator[],在std::cout上调用N次operator<<

第二个循环:在std::vector对象上调用beginend成员函数,N次取消引用vector<int>::iterator和N次operator<<std::cout.

以下操作:sizeoperator[]beginend 和取消引用 vector<int>::iterator 具有恒定的复杂性。换句话说,没什么。 operator[] 可能会导致将一些数字添加到基地址,而向量迭代器很可能是原始指针。很明显,在标准输出上打印东西是这里的主导因素,在两种情况下都是一样的。一般而言,没有理由相信其中之一会比另一个更快。

只是为了好玩,我在我的笔记本电脑上试了一下,我没有发现任何值得讨论的区别。

Gdb 反汇编:gcc 4.8 (g++ -g -std=c++11 -O2 file.cc):

    Dump of assembler code for function main():
    5       {
       0x0000000000400880 <+0>:     push   %r12
       0x000000000040088d <+13>:    push   %rbp
       0x000000000040088e <+14>:    push   %rbx

    6         std::vector<int> myVec = { 1, 2, 3, 5, 6, 7 }; // some vector of ints
    7
    8         for (int i(0), j(myVec.size()); i < j; ++i) std::cout << myVec[i];
       0x0000000000400887 <+7>:     mov    [=10=]x6,%r12d
       0x000000000040088f <+15>:    xor    %ebx,%ebx
       0x00000000004008c0 <+64>:    mov    0x0(%rbp,%rbx,4),%esi
       0x00000000004008c4 <+68>:    mov    [=10=]x601080,%edi
       0x00000000004008c9 <+73>:    callq  0x4007d0 <_ZNSolsEi@plt>
       0x00000000004008ce <+78>:    add    [=10=]x1,%rbx
       0x00000000004008d2 <+82>:    cmp    %ebx,%r12d
       0x00000000004008d5 <+85>:    jg     0x4008c0 <main()+64>

    9
    10        std::cout << std::endl;
    11
    12        for (std::vector<int>::iterator it(myVec.begin()), j(myVec.end()); it != j; ++it) std::cout << *it;
       0x00000000004008f0 <+112>:   mov    (%rbx),%esi
       0x00000000004008f2 <+114>:   mov    [=10=]x601080,%edi
       0x00000000004008f7 <+119>:   callq  0x4007d0 <_ZNSolsEi@plt>
       0x00000000004008fc <+124>:   add    [=10=]x4,%rbx
       0x0000000000400900 <+128>:   cmp    %r12,%rbx
       0x0000000000400903 <+131>:   jne    0x4008f0 <main()+112>

    13
    14        std::cout << std::endl;
    15      }
       0x000000000040091c <+156>:   pop    %rbx
       0x000000000040091d <+157>:   pop    %rbp
       0x000000000040091e <+158>:   xor    %eax,%eax
       0x0000000000400920 <+160>:   pop    %r12
       0x0000000000400922 <+162>:   retq

本题涉及的依赖性和未知数太多。

在第一种情况下,您正在调用一个函数来访问一个元素。可以优化该函数以消除函数调用的开销并归结为乘法和加法运算。

第二种情况涉及取消引用指针以访问内存。

这里的一个问题是处理器的能力。许多现代处理器都可以通过取消引用(使用零索引)或通过使用单个指令从指针进行索引来从内存中获取值。

另一个影响是向量是否适合处理器的缓存行(如果它有数据缓存)。缓存行之外的任何数据都需要额外的处理逻辑。

如果一种方法比另一种方法快,则可以忽略不计,除非您执行 1E+10 次迭代。差异通常以纳秒为单位。您的程序将浪费更多时间被 OS 换出、调用函数或执行 I/O。一个例子:相差10ns。您的程序等待 0.5 秒让用户输入一个值。每次迭代 10ns 的增益被浪费在等待用户输入上。

通常,此类微优化仅对高性能应用程序或必须满足关键期限的应用程序至关重要。例如,如果嵌入式系统必须每毫秒读取一次传感器,那么读取传感器所花费的时间越少,系统其余部分执行的时间就越多。

编辑 1:
像这样的微优化还取决于数据的来源。内存离处理器核心越远,获取越慢。例如,从同一块硅片上的内存中获取数据比从子板上的闪存设备中获取数据要快。