11

这是我在这里发布的第一个问题,所以我希望我不会做错任何事情。

我的问题涉及现代风格的 C++11 循环(std::for_each基于范围的 for)与旧式 C++ 循环(for (...; ...; ...))的性能。根据我的理解,在我看来,现代 C++ 的座右铭是“表现力不妥协”。现代 C++ 风格带来安全、干净和快速的代码,几乎没有性能损失,并且可能比旧式 C++ 获得性能提升。

现在我做了一个小测试来评估这个增益对循环的影响有多大。首先我写了以下三个函数:

using namespace std;

void foo(vector<double>& v)
{
    for (size_t i = 0; i < v.size(); i++)
    {
        v[i] /= 42;
    }
}

void bar(vector<double>& v)
{
    for (auto& x : v)
    {
        x /= 42;
    }
}

void wee(vector<double>& v)
{
    for_each(begin(v), end(v), [] (double& x)
    {
        x /= 42;
    });
}

main()然后我通过这种方式调用它们来比较它们的性能(正确注释/取消注释循环内的三行:

vector<double> make_vector()
{
    vector<double> v;
    for (int i = 0; i < 30000; i++) { v.push_back(i); }
    return v;
}

int main()
{
    time_t start = clock();

    auto v = make_vector();
    for (int i = 0; i <= 50000; i++) 
    { 
        // UNCOMMENT THE FUNCTION CALL TO BE TESTED, COMMENT THE OTHERS

        foo(v);
        // bar(v); 
        // wee(v);
    }

    time_t end = clock();
    cout << (end - start) << endl;

    return 0;
}

通过注释/取消注释main()'s 循环中的行并使用旧式循环作为基线,平均每个版本的程序执行 10 次以上,基于范围的 for 循环的性能差约 1.9 倍,而基于std::for_each并且 lambdas 的性能差了约 2.3 倍。

我使用 Clang 3.2 来编译它,我还没有尝试过 MS VC11(我正在使用 WinXP)。

考虑到我期望获得可比较的执行时间,我的问题是:

  1. 我做了什么明显错误的事情吗?
  2. 如果不是,那么 2 倍的性能损失难道不是不采用现代风格循环的好理由吗?

我想指出,我确实相信以现代 C++ 风格编写的代码的清晰度和安全性会为可能的性能损失带来回报,但我完全不同意在清晰度/安全性之间没有权衡的说法一边和另一边的表现。

我错过了什么吗?

4

2 回答 2

12

看起来只有当您没有在编译器中启用优化时才会出现差异。

使用 Clang,您可以使用-O[0-3]标志启用优化。

于 2012-12-27T15:04:35.687 回答
10

Mankarse是对的 - 很可能您没有启用优化。

实际上在 Clang 上,它们在主循环中产生几乎相同的结果 ASM 代码,并且前后代码的差异很小。

我测试了四个版本:hand_loop_index, hand_loop_iterator, range_based_for,for_each_algorithm

hand_loop_iterator,range_based_forfor_each_algorithm- 对于完整的函数体,这三个确实产生完全相同的结果 ASM,唯一的区别在于标签的名称。

即用迭代器手工编写的 for 循环产生与基于范围的 for 和 std::for_each 完全相同的 ASM 代码。

带索引的循环和带迭代器的循环之间存在一些差异。

两种情况下的主循环几乎相同。唯一的小区别是迭代器rdx使用版本寄存器而不是rsi.

索引版本:

.LBB0_7:                                # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    movupd  -48(%rsi), %xmm1
    movupd  -32(%rsi), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -48(%rsi)
    movupd  %xmm2, -32(%rsi)
    movupd  -16(%rsi), %xmm1
    movupd  (%rsi), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -16(%rsi)
    movupd  %xmm2, (%rsi)
    addq    $64, %rsi
    addq    $-8, %rdi
    jne .LBB0_7

迭代器版本:

.LBB1_6:                                # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    movupd  -48(%rdx), %xmm1
    movupd  -32(%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -48(%rdx)
    movupd  %xmm2, -32(%rdx)
    movupd  -16(%rdx), %xmm1
    movupd  (%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -16(%rdx)
    movupd  %xmm2, (%rdx)
    addq    $64, %rdx
    addq    $-8, %rsi
    jne .LBB1_6

索引与迭代器版本的前/后代码有很多差异,但对于足够大的数组,它不应该对总结果时间产生太大影响。

带有 ASM 输出的 Coliru 上的 LIVE DEMO

#include <algorithm>
#include <iterator>
#include <vector>

using namespace std;

void hand_loop_index(vector<double> &v)
{
    for (size_t i = 0; i < v.size(); ++i)
    {
        v[i] /= 42;
    }
}

void hand_loop_iterator(vector<double> &v)
{
    for (auto first = begin(v), last = end(v); first!=last; ++first)
    {
        *first /= 42;
    }
}

void range_based_for(vector<double> &v)
{
    for (auto &x : v)
    {
        x /= 42;
    }
}

void for_each_algorithm(vector<double> &v)
{
    for_each(begin(v), end(v), [] (double &x)
    {
        x /= 42;
    });
}

结果 ASM:

# clang++ -std=c++1z -O3 -Wall -pedantic -pthread main.cpp -S
    .text
    .file   "main.cpp"
    .section    .rodata.cst16,"aM",@progbits,16
    .align  16
.LCPI0_0:
    .quad   4631107791820423168     # double 4.200000e+01
    .quad   4631107791820423168     # double 4.200000e+01
    .section    .rodata.cst8,"aM",@progbits,8
    .align  8
.LCPI0_1:
    .quad   4631107791820423168     # double 42
    .text
    .globl  _Z15hand_loop_indexRSt6vectorIdSaIdEE
    .align  16, 0x90
    .type   _Z15hand_loop_indexRSt6vectorIdSaIdEE,@function
_Z15hand_loop_indexRSt6vectorIdSaIdEE:  # @_Z15hand_loop_indexRSt6vectorIdSaIdEE
    .cfi_startproc
# BB#0:
    movq    (%rdi), %rax
    movq    8(%rdi), %rcx
    subq    %rax, %rcx
    je  .LBB0_11
# BB#1:                                 # %.lr.ph
    sarq    $3, %rcx
    cmpq    $1, %rcx
    movl    $1, %edx
    cmovaq  %rcx, %rdx
    xorl    %edi, %edi
    testq   %rdx, %rdx
    je  .LBB0_10
# BB#2:                                 # %overflow.checked
    xorl    %edi, %edi
    movq    %rdx, %r8
    andq    $-4, %r8
    je  .LBB0_9
# BB#3:                                 # %vector.body.preheader
    cmpq    $1, %rcx
    movl    $1, %edi
    cmovaq  %rcx, %rdi
    addq    $-4, %rdi
    movq    %rdi, %rsi
    shrq    $2, %rsi
    xorl    %r9d, %r9d
    btq $2, %rdi
    jb  .LBB0_5
# BB#4:                                 # %vector.body.prol
    movupd  (%rax), %xmm0
    movupd  16(%rax), %xmm1
    movapd  .LCPI0_0(%rip), %xmm2   # xmm2 = [4.200000e+01,4.200000e+01]
    divpd   %xmm2, %xmm0
    divpd   %xmm2, %xmm1
    movupd  %xmm0, (%rax)
    movupd  %xmm1, 16(%rax)
    movl    $4, %r9d
.LBB0_5:                                # %vector.body.preheader.split
    testq   %rsi, %rsi
    je  .LBB0_8
# BB#6:                                 # %vector.body.preheader.split.split
    cmpq    $1, %rcx
    movl    $1, %edi
    cmovaq  %rcx, %rdi
    andq    $-4, %rdi
    subq    %r9, %rdi
    leaq    48(%rax,%r9,8), %rsi
    movapd  .LCPI0_0(%rip), %xmm0   # xmm0 = [4.200000e+01,4.200000e+01]
    .align  16, 0x90
.LBB0_7:                                # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    movupd  -48(%rsi), %xmm1
    movupd  -32(%rsi), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -48(%rsi)
    movupd  %xmm2, -32(%rsi)
    movupd  -16(%rsi), %xmm1
    movupd  (%rsi), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -16(%rsi)
    movupd  %xmm2, (%rsi)
    addq    $64, %rsi
    addq    $-8, %rdi
    jne .LBB0_7
.LBB0_8:
    movq    %r8, %rdi
.LBB0_9:                                # %middle.block
    cmpq    %rdi, %rdx
    je  .LBB0_11
    .align  16, 0x90
.LBB0_10:                               # %scalar.ph
                                        # =>This Inner Loop Header: Depth=1
    movsd   (%rax,%rdi,8), %xmm0    # xmm0 = mem[0],zero
    divsd   .LCPI0_1(%rip), %xmm0
    movsd   %xmm0, (%rax,%rdi,8)
    incq    %rdi
    cmpq    %rcx, %rdi
    jb  .LBB0_10
.LBB0_11:                               # %._crit_edge
    retq
.Lfunc_end0:
    .size   _Z15hand_loop_indexRSt6vectorIdSaIdEE, .Lfunc_end0-_Z15hand_loop_indexRSt6vectorIdSaIdEE
    .cfi_endproc

.section    .rodata.cst16,"aM",@progbits,16
    .align  16
.LCPI1_0:
    .quad   4631107791820423168     # double 4.200000e+01
    .quad   4631107791820423168     # double 4.200000e+01
    .section    .rodata.cst8,"aM",@progbits,8
    .align  8
.LCPI1_1:
    .quad   4631107791820423168     # double 42
    .text
    .globl  _Z18hand_loop_iteratorRSt6vectorIdSaIdEE
    .align  16, 0x90
    .type   _Z18hand_loop_iteratorRSt6vectorIdSaIdEE,@function
_Z18hand_loop_iteratorRSt6vectorIdSaIdEE: # @_Z18hand_loop_iteratorRSt6vectorIdSaIdEE
    .cfi_startproc
# BB#0:
    movq    (%rdi), %rdx
    movq    8(%rdi), %rax
    cmpq    %rax, %rdx
    je  .LBB1_11
# BB#1:                                 # %.lr.ph.preheader
    movabsq $4611686018427387900, %rsi # imm = 0x3FFFFFFFFFFFFFFC
    leaq    -8(%rax), %rcx
    subq    %rdx, %rcx
    shrq    $3, %rcx
    incq    %rcx
    xorl    %edi, %edi
    movq    %rcx, %r9
    andq    %rsi, %r9
    je  .LBB1_8
# BB#2:                                 # %vector.body.preheader
    andq    %rcx, %rsi
    leaq    -4(%rsi), %rdi
    movq    %rdi, %r11
    shrq    $2, %r11
    xorl    %r10d, %r10d
    btq $2, %rdi
    jb  .LBB1_4
# BB#3:                                 # %vector.body.prol
    movupd  (%rdx), %xmm0
    movupd  16(%rdx), %xmm1
    movapd  .LCPI1_0(%rip), %xmm2   # xmm2 = [4.200000e+01,4.200000e+01]
    divpd   %xmm2, %xmm0
    divpd   %xmm2, %xmm1
    movupd  %xmm0, (%rdx)
    movupd  %xmm1, 16(%rdx)
    movl    $4, %r10d
.LBB1_4:                                # %vector.body.preheader.split
    leaq    (%rdx,%r9,8), %r8
    testq   %r11, %r11
    je  .LBB1_7
# BB#5:                                 # %vector.body.preheader.split.split
    subq    %r10, %rsi
    leaq    48(%rdx,%r10,8), %rdx
    movapd  .LCPI1_0(%rip), %xmm0   # xmm0 = [4.200000e+01,4.200000e+01]
    .align  16, 0x90
.LBB1_6:                                # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    movupd  -48(%rdx), %xmm1
    movupd  -32(%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -48(%rdx)
    movupd  %xmm2, -32(%rdx)
    movupd  -16(%rdx), %xmm1
    movupd  (%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -16(%rdx)
    movupd  %xmm2, (%rdx)
    addq    $64, %rdx
    addq    $-8, %rsi
    jne .LBB1_6
.LBB1_7:
    movq    %r8, %rdx
    movq    %r9, %rdi
.LBB1_8:                                # %middle.block
    cmpq    %rdi, %rcx
    je  .LBB1_11
# BB#9:
    movsd   .LCPI1_1(%rip), %xmm0   # xmm0 = mem[0],zero
    .align  16, 0x90
.LBB1_10:                               # %.lr.ph
                                        # =>This Inner Loop Header: Depth=1
    movsd   (%rdx), %xmm1           # xmm1 = mem[0],zero
    divsd   %xmm0, %xmm1
    movsd   %xmm1, (%rdx)
    addq    $8, %rdx
    cmpq    %rdx, %rax
    jne .LBB1_10
.LBB1_11:                               # %._crit_edge
    retq
.Lfunc_end1:
    .size   _Z18hand_loop_iteratorRSt6vectorIdSaIdEE, .Lfunc_end1-_Z18hand_loop_iteratorRSt6vectorIdSaIdEE
    .cfi_endproc

.section    .rodata.cst16,"aM",@progbits,16
    .align  16
.LCPI2_0:
    .quad   4631107791820423168     # double 4.200000e+01
    .quad   4631107791820423168     # double 4.200000e+01
    .section    .rodata.cst8,"aM",@progbits,8
    .align  8
.LCPI2_1:
    .quad   4631107791820423168     # double 42
    .text
    .globl  _Z15range_based_forRSt6vectorIdSaIdEE
    .align  16, 0x90
    .type   _Z15range_based_forRSt6vectorIdSaIdEE,@function
_Z15range_based_forRSt6vectorIdSaIdEE:  # @_Z15range_based_forRSt6vectorIdSaIdEE
    .cfi_startproc
# BB#0:
    movq    (%rdi), %rdx
    movq    8(%rdi), %rax
    cmpq    %rax, %rdx
    je  .LBB2_11
# BB#1:                                 # %.lr.ph.preheader
    movabsq $4611686018427387900, %rsi # imm = 0x3FFFFFFFFFFFFFFC
    leaq    -8(%rax), %rcx
    subq    %rdx, %rcx
    shrq    $3, %rcx
    incq    %rcx
    xorl    %edi, %edi
    movq    %rcx, %r9
    andq    %rsi, %r9
    je  .LBB2_8
# BB#2:                                 # %vector.body.preheader
    andq    %rcx, %rsi
    leaq    -4(%rsi), %rdi
    movq    %rdi, %r11
    shrq    $2, %r11
    xorl    %r10d, %r10d
    btq $2, %rdi
    jb  .LBB2_4
# BB#3:                                 # %vector.body.prol
    movupd  (%rdx), %xmm0
    movupd  16(%rdx), %xmm1
    movapd  .LCPI2_0(%rip), %xmm2   # xmm2 = [4.200000e+01,4.200000e+01]
    divpd   %xmm2, %xmm0
    divpd   %xmm2, %xmm1
    movupd  %xmm0, (%rdx)
    movupd  %xmm1, 16(%rdx)
    movl    $4, %r10d
.LBB2_4:                                # %vector.body.preheader.split
    leaq    (%rdx,%r9,8), %r8
    testq   %r11, %r11
    je  .LBB2_7
# BB#5:                                 # %vector.body.preheader.split.split
    subq    %r10, %rsi
    leaq    48(%rdx,%r10,8), %rdx
    movapd  .LCPI2_0(%rip), %xmm0   # xmm0 = [4.200000e+01,4.200000e+01]
    .align  16, 0x90
.LBB2_6:                                # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    movupd  -48(%rdx), %xmm1
    movupd  -32(%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -48(%rdx)
    movupd  %xmm2, -32(%rdx)
    movupd  -16(%rdx), %xmm1
    movupd  (%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -16(%rdx)
    movupd  %xmm2, (%rdx)
    addq    $64, %rdx
    addq    $-8, %rsi
    jne .LBB2_6
.LBB2_7:
    movq    %r8, %rdx
    movq    %r9, %rdi
.LBB2_8:                                # %middle.block
    cmpq    %rdi, %rcx
    je  .LBB2_11
# BB#9:
    movsd   .LCPI2_1(%rip), %xmm0   # xmm0 = mem[0],zero
    .align  16, 0x90
.LBB2_10:                               # %.lr.ph
                                        # =>This Inner Loop Header: Depth=1
    movsd   (%rdx), %xmm1           # xmm1 = mem[0],zero
    divsd   %xmm0, %xmm1
    movsd   %xmm1, (%rdx)
    addq    $8, %rdx
    cmpq    %rdx, %rax
    jne .LBB2_10
.LBB2_11:                               # %._crit_edge
    retq
.Lfunc_end2:
    .size   _Z15range_based_forRSt6vectorIdSaIdEE, .Lfunc_end2-_Z15range_based_forRSt6vectorIdSaIdEE
    .cfi_endproc

.section    .rodata.cst16,"aM",@progbits,16
    .align  16
.LCPI3_0:
    .quad   4631107791820423168     # double 4.200000e+01
    .quad   4631107791820423168     # double 4.200000e+01
    .section    .rodata.cst8,"aM",@progbits,8
    .align  8
.LCPI3_1:
    .quad   4631107791820423168     # double 42
    .text
    .globl  _Z18for_each_algorithmRSt6vectorIdSaIdEE
    .align  16, 0x90
    .type   _Z18for_each_algorithmRSt6vectorIdSaIdEE,@function
_Z18for_each_algorithmRSt6vectorIdSaIdEE: # @_Z18for_each_algorithmRSt6vectorIdSaIdEE
    .cfi_startproc
# BB#0:
    movq    (%rdi), %rdx
    movq    8(%rdi), %rax
    cmpq    %rax, %rdx
    je  .LBB3_11
# BB#1:                                 # %.lr.ph.i.preheader
    movabsq $4611686018427387900, %rsi # imm = 0x3FFFFFFFFFFFFFFC
    leaq    -8(%rax), %rcx
    subq    %rdx, %rcx
    shrq    $3, %rcx
    incq    %rcx
    xorl    %edi, %edi
    movq    %rcx, %r9
    andq    %rsi, %r9
    je  .LBB3_8
# BB#2:                                 # %vector.body.preheader
    andq    %rcx, %rsi
    leaq    -4(%rsi), %rdi
    movq    %rdi, %r11
    shrq    $2, %r11
    xorl    %r10d, %r10d
    btq $2, %rdi
    jb  .LBB3_4
# BB#3:                                 # %vector.body.prol
    movupd  (%rdx), %xmm0
    movupd  16(%rdx), %xmm1
    movapd  .LCPI3_0(%rip), %xmm2   # xmm2 = [4.200000e+01,4.200000e+01]
    divpd   %xmm2, %xmm0
    divpd   %xmm2, %xmm1
    movupd  %xmm0, (%rdx)
    movupd  %xmm1, 16(%rdx)
    movl    $4, %r10d
.LBB3_4:                                # %vector.body.preheader.split
    leaq    (%rdx,%r9,8), %r8
    testq   %r11, %r11
    je  .LBB3_7
# BB#5:                                 # %vector.body.preheader.split.split
    subq    %r10, %rsi
    leaq    48(%rdx,%r10,8), %rdx
    movapd  .LCPI3_0(%rip), %xmm0   # xmm0 = [4.200000e+01,4.200000e+01]
    .align  16, 0x90
.LBB3_6:                                # %vector.body
                                        # =>This Inner Loop Header: Depth=1
    movupd  -48(%rdx), %xmm1
    movupd  -32(%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -48(%rdx)
    movupd  %xmm2, -32(%rdx)
    movupd  -16(%rdx), %xmm1
    movupd  (%rdx), %xmm2
    divpd   %xmm0, %xmm1
    divpd   %xmm0, %xmm2
    movupd  %xmm1, -16(%rdx)
    movupd  %xmm2, (%rdx)
    addq    $64, %rdx
    addq    $-8, %rsi
    jne .LBB3_6
.LBB3_7:
    movq    %r8, %rdx
    movq    %r9, %rdi
.LBB3_8:                                # %middle.block
    cmpq    %rdi, %rcx
    je  .LBB3_11
# BB#9:
    movsd   .LCPI3_1(%rip), %xmm0   # xmm0 = mem[0],zero
    .align  16, 0x90
.LBB3_10:                               # %.lr.ph.i
                                        # =>This Inner Loop Header: Depth=1
    movsd   (%rdx), %xmm1           # xmm1 = mem[0],zero
    divsd   %xmm0, %xmm1
    movsd   %xmm1, (%rdx)
    addq    $8, %rdx
    cmpq    %rdx, %rax
    jne .LBB3_10
.LBB3_11:                               # %_ZSt8for_eachIN9__gnu_cxx17__normal_iteratorIPdSt6vectorIdSaIdEEEEZ18for_each_algorithmR5_E3$_0ET0_T_SA_S9_.exit
    retq
.Lfunc_end3:
    .size   _Z18for_each_algorithmRSt6vectorIdSaIdEE, .Lfunc_end3-_Z18for_each_algorithmRSt6vectorIdSaIdEE
    .cfi_endproc

    .ident  "clang version 3.7.0 (tags/RELEASE_370/final 246979)"
    .section    ".note.GNU-stack","",@progbits
于 2015-10-25T19:17:51.747 回答