为什么返回std::pair
或boost::tuple
的效率低于通过引用返回的效率?在我测试的实际代码中,通过非const引用而不是内核中的std::pair
设置数据可以将代码加速20%。
作为一个实验,我研究了三个最简单的情况,包括将两个(预定义的)整数添加到两个整数:
使用内部内联函数通过引用
使用两个内部内联函数按值返回整数
使用内部内联函数返回复制到结果的std ::对。
使用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语句的原因是它在很多情况下使函数很多的意图更清晰,特别是当输入其他参数时以及那些需要修改的内容。
答案 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中正在发生变化,所以也许这会有所帮助。