Pra*_*tic 9 c++ performance assembly gcc c++14
使用gcc 5.3,以下示例中的两个函数都会生成一个调用memmove
.生成一个memcpy
?是不合适的?
#include <vector>
int blackhole(const std::vector<int>&);
int copy_vec1(const std::vector<int>& v1) {
const std::vector<int> v2{v1.begin(), v1.end()};
return blackhole(v2);
}
int copy_vec2(const std::vector<int>& v1) {
const auto v2 = v1;
return blackhole(v2);
}
Run Code Online (Sandbox Code Playgroud)
我尝试使用g ++ 6.1.0编译这段代码.我完全不确定细节,但我认为memmove
调用不是由编译器直接生成的; 相反,它是在实现的代码中<vector>
.
当我使用预处理代码时
/o/apps/gcc-6.1.0/bin/g++ -E -std=c++14 c.cpp
Run Code Online (Sandbox Code Playgroud)
我看到两个电话__builtin_memmove
,都来自.../include/c++/6.1.0/bits/stl_algobase.h
.看看那个头文件,我看到这个评论:
// All of these auxiliary structs serve two purposes. (1) Replace
// calls to copy with memmove whenever possible. (Memmove, not memcpy,
// because the input and output ranges are permitted to overlap.)
// (2) If we're using random access iterators, then write the loop as
// a for loop with an explicit count.
Run Code Online (Sandbox Code Playgroud)
我认为发生的事情是,用于复制向量的代码更普遍适用于可以重叠的副本(例如对std::move
(?)的调用).
(我还没有确认memmove
汇编列表中出现的呼叫对应于__builtin_memmove
呼叫stl_algobase.h
.我邀请其他人跟进这一点.)
根据实现情况,memmove()
可能会有一些相对的开销memcpy()
,但差别很小.可能只是不值得为不能重叠的副本创建特例代码.
TL; DR GCC不优化对memmove
内部的调用std::copy
.当使用两个C风格的数组时,它确实如此.替换&v2[0]
为*v2.data()
允许它优化成一个memcpy
.
你的例子非常嘈杂,所以让我们把它剥掉:
#include <vector>
#include <algorithm>
int a[5];
int b[5];
std::vector<int> v2;
Run Code Online (Sandbox Code Playgroud)
我故意将变量放在文件范围内,以防止优化它们而不必处理volatile
语义.
首先让我们试试:
std::copy(&a[0], &a[5], &b[0]);
Run Code Online (Sandbox Code Playgroud)
随着-O3 -fdump-tree-optimized
这变成:
__builtin_memcpy (&b[0], &a[0], 20);
Run Code Online (Sandbox Code Playgroud)
单步执行GDB向我们展示:
Breakpoint 1, main () at test.cpp:9
9 std::copy(&a[0], &a[0] + 5, &b[0]);
(gdb) s
std::copy<int*, int*> (__result=0x601080 <b>, __last=0x6010b4, __first=0x6010a0 <a>) at test.cpp:9
9 std::copy(&a[0], &a[0] + 5, &b[0]);
(gdb) s
std::__copy_move_a2<false, int*, int*> (__result=0x601080 <b>, __last=0x6010b4, __first=0x6010a0 <a>) at test.cpp:9
9 std::copy(&a[0], &a[0] + 5, &b[0]);
(gdb) s
std::__copy_move_a<false, int*, int*> (__result=<optimized out>, __last=<optimized out>, __first=<optimized out>) at test.cpp:9
9 std::copy(&a[0], &a[0] + 5, &b[0]);
(gdb) s
std::__copy_move<false, true, std::random_access_iterator_tag>::__copy_m<int> (__result=<optimized out>, __last=<optimized out>,
__first=<optimized out>) at /usr/include/c++/5.3.1/bits/stl_algobase.h:382
382 __builtin_memmove(__result, __first, sizeof(_Tp) * _Num);
(gdb) s
main () at test.cpp:10
10 }
Run Code Online (Sandbox Code Playgroud)
等等用memmove
吗?!好吧,让我们继续吧.
关于什么:
std::copy(&a[0], &a[5], v2.begin());
Run Code Online (Sandbox Code Playgroud)
好的,这让我们memmove
:
int * _2;
<bb 2>:
_2 = MEM[(int * const &)&v2];
__builtin_memmove (_2, &a[0], 20);
Run Code Online (Sandbox Code Playgroud)
如果我们这样做,这反映在组装中-S
.单步执行GDB向我们展示了这个过程:
(gdb)
Breakpoint 1, main () at test.cpp:9
9 {
(gdb) s
10 std::copy(&a[0], &a[5], &v2[0]);
(gdb) s
std::copy<int*, int*> (__result=<optimized out>, __last=0x6010d4, __first=0x6010c0 <a>) at test.cpp:10
10 std::copy(&a[0], &a[5], &v2[0]);
(gdb) s
std::__copy_move_a2<false, int*, int*> (__result=<optimized out>, __last=0x6010d4, __first=0x6010c0 <a>) at test.cpp:10
10 std::copy(&a[0], &a[5], &v2[0]);
(gdb) s
std::__copy_move_a<false, int*, int*> (__result=<optimized out>, __last=<optimized out>, __first=<optimized out>) at test.cpp:10
10 std::copy(&a[0], &a[5], &v2[0]);
(gdb) s
std::__copy_move<false, true, std::random_access_iterator_tag>::__copy_m<int> (__result=<optimized out>, __last=<optimized out>,
__first=<optimized out>) at /usr/include/c++/5.3.1/bits/stl_algobase.h:382
382 __builtin_memmove(__result, __first, sizeof(_Tp) * _Num);
(gdb) s
__memmove_ssse3 () at ../sysdeps/x86_64/multiarch/memcpy-ssse3.S:55
Run Code Online (Sandbox Code Playgroud)
啊,我明白了.它使用memcpy
C库提供的优化例程.但等一下,这没有意义.memmove
并且memcpy
是两件不同的事情!
查看此例程的源代码,我们看到几乎没有检查:
85 #ifndef USE_AS_MEMMOVE
86 cmp %dil, %sil
87 jle L(copy_backward)
88 #endif
Run Code Online (Sandbox Code Playgroud)
GDB确认它将其视为memmove
:
55 mov %rdi, %rax
(gdb) s
61 cmp %rsi, %rdi
(gdb) s
62 jb L(copy_forward)
(gdb) s
63 je L(write_0bytes)
Run Code Online (Sandbox Code Playgroud)
但是如果我们&v2[0]
用*v2.data()
它代替它就不会叫GLIBC memmove
.发生什么了?
好了v2[0]
,v2.begin()
返回迭代器,同时v2.data()
返回一个指向内存的直接指针.我认为这是出于某种原因阻止GCC优化memmove
成a memcpy
.[引证需要]
为实现者使用的理由memmove
了memcpy
可能在这种情况下是有缺陷的.
memmove
不同之处在于memcpy
存储区域memmove
可能重叠(因此概念上效率稍低).
memcpy
具有两个存储区域不得重叠的约束.
在向量的复制构造函数的情况下,内存区域永远不会重叠,因此可以认为这memcpy
将是更好的选择.