返回std :: pair与非const引用的传递

时间:2009-12-08 19:09:24

标签: c++ optimization assembly

为什么返回std::pairboost::tuple的效率低于通过引用返回的效率?在我测试的实际代码中,通过非const引用而不是内核中的std::pair设置数据可以将代码加速20%。

作为一个实验,我研究了三个最简单的情况,包括将两个(预定义的)整数添加到两个整数:

  1. 使用内部内联函数通过引用

  2. 修改整数
  3. 使用两个内部内联函数按值返回整数

  4. 使用内部内联函数返回复制到结果的std ::对。

  5. 使用g++ -c $x -Wall -Wextra -O2 -S进行编译会产生相同的汇编代码,以便按引用传递并按值返回整数:

    __Z7getPairiRiS_:
    LFB19:
        pushq   %rbp
    LCFI0:
        leal    1023(%rdi), %eax
        addl    $31, %edi
        movl    %eax, (%rsi)
        movq    %rsp, %rbp
    LCFI1:
        movl    %edi, (%rdx)
        leave
        ret
    

    (通过参考代码:

    #include <utility>
    
    inline void myGetPair(const int inp, int& a, int& b) {
        a = 1023 + inp;
        b = 31 + inp;
    }
    
    void getPair(const int inp, int& a, int& b) {
        myGetPair(inp, a, b);
    }
    

    使用个别右值:

    #include <utility>
    
    inline int myGetPair1(int inp) {
        return 1023 + inp;
    }
    
    inline int myGetPair2(int inp) {
        return 31 + inp;
    }
    
    void getPair(const int inp, int& a, int& b) {
        a = myGetPair1(inp);
        b = myGetPair2(inp);
    }
    

    然而,使用std :: pair会添加五个额外的汇编语句:

    __Z7getPairiRiS_:
    LFB18:
        leal    31(%rdi), %eax
        addl    $1023, %edi
        pushq   %rbp
    LCFI0:
        salq    $32, %rax
        movq    %rsp, %rbp
    LCFI1:
        orq %rdi, %rax
        movq    %rax, %rcx
        movl    %eax, (%rsi)
        shrq    $32, %rcx
        movl    %ecx, (%rdx)
        leave
        ret
    

    该代码几乎与前面的示例一样简单:

    #include <utility>
    
    inline std::pair<int,int> myGetPair(int inp) {
        return std::make_pair(1023 + inp, 31 + inp);
    }
    
    void getPair(const int inp, int& a, int& b) {
        std::pair<int,int> result = myGetPair(inp);
    
        a = result.first;
        b = result.second;
    }
    

    知道编译器内部工作原理的人是否可以帮助解决这个问题? boost tuple page引用了元组与传递引用的性能损失,但没有一个链接的文章回答这个问题。

    我更喜欢std :: pair到这些pass-by-reference语句的原因是它在很多情况下使函数很多的意图更清晰,特别是当输入其他参数时以及那些需要修改的内容。

6 个答案:

答案 0 :(得分:5)

糟糕的优化。让我们希望有一天编译器会更好。

答案 1 :(得分:5)

我尝试使用VC ++ 2008,使用cl.exe /c /O2 /FAs foo.cpp(“仅编译并且不链接”,“优化速度”和“在注释中使用匹配的源代码行转储汇编输出”)。这是getLine()最终的结果。

“byref”版本:

PUBLIC  ?getPair@@YAXHAAH0@Z                ; getPair
; Function compile flags: /Ogtpy
;   COMDAT ?getPair@@YAXHAAH0@Z
_TEXT   SEGMENT
_inp$ = 8                       ; size = 4
_a$ = 12                        ; size = 4
_b$ = 16                        ; size = 4
?getPair@@YAXHAAH0@Z PROC               ; getPair, COMDAT

; 9    :     myGetPair(inp, a, b);

    mov eax, DWORD PTR _inp$[esp-4]
    mov edx, DWORD PTR _a$[esp-4]
    lea ecx, DWORD PTR [eax+1023]
    mov DWORD PTR [edx], ecx
    mov ecx, DWORD PTR _b$[esp-4]
    add eax, 31                 ; 0000001fH
    mov DWORD PTR [ecx], eax

; 10   : }

    ret 0
?getPair@@YAXHAAH0@Z ENDP               ; getPair

“byval”std::pair - 返回版本:

PUBLIC  ?getPair@@YAXHAAH0@Z                ; getPair
; Function compile flags: /Ogtpy
;   COMDAT ?getPair@@YAXHAAH0@Z
_TEXT   SEGMENT
_inp$ = 8                       ; size = 4
_a$ = 12                        ; size = 4
_b$ = 16                        ; size = 4
?getPair@@YAXHAAH0@Z PROC               ; getPair, COMDAT

; 8    :     std::pair<int,int> result = myGetPair(inp);

    mov eax, DWORD PTR _inp$[esp-4]

; 9    : 
; 10   :     a = result.first;

    mov edx, DWORD PTR _a$[esp-4]
    lea ecx, DWORD PTR [eax+1023]
    mov DWORD PTR [edx], ecx

; 11   :     b = result.second;

    mov ecx, DWORD PTR _b$[esp-4]
    add eax, 31                 ; 0000001fH
    mov DWORD PTR [ecx], eax

; 12   : }

    ret 0
?getPair@@YAXHAAH0@Z ENDP               ; getPair

如您所见,实际装配是相同的;唯一的区别在于错误的名称和评论。

答案 2 :(得分:4)

您使用的是什么编译器?在gcc 4.4.2中,我得到了完全相同的结果(操作码的操作码),用于传递引用和返回值对。即:

mov 0x4(%esp),%eax
mov 0x8(%esp),%edx
lea 0x3ff(%eax),%ecx
add $0x1f,%eax
mov %ecx,(%edx)
mov 0xc(%esp),%edx
mov %eax,(%edx)
ret
lea 0x0(%esi),%esi

这是-O2 -fomit-frame-pointer。看起来编译器理解这里的意图并且没有做任何多余的事情。也许你应该升级你的:P

答案 3 :(得分:3)

按值返回会强制在堆栈上构造未命名的临时值,并将其值赋值给局部变量。

答案 4 :(得分:3)

大多数C ++程序员都非常重视表达的清晰度,远远高于一些可疑的“效率”问题。但是如果你必须知道,返回引用几乎总是由编译器通过返回指针来实现。

答案 5 :(得分:3)

这主要是猜测,但我认为编译器在优化std::pair时遇到困难的一个原因是pair有一个非平凡的构造函数。这使得它成为非POD,并且编译器在优化时不能做出过于激进的假设。

POD-ness规则在C ++ 0x中正在发生变化,所以也许这会有所帮助。