使用gcc 5.3,以下示例中的两个函数都会生成对li
[[1]]
[1] "X" "A"
[[2]]
[1] "X" "B"
[[3]]
[1] "X" "C"
[[4]]
[1] "X" "D"
[[5]]
[1] "X" "E"
[[6]]
[1] "X" "X"
的调用。生成lapply(li, function(pair) pair[sample(1:2)])[sample(1:6)]
[[1]]
[1] "X" "D"
[[2]]
[1] "B" "X"
[[3]]
[1] "E" "X"
[[4]]
[1] "X" "X"
[[5]]
[1] "X" "A"
[[6]]
[1] "C" "X"
memmove
答案 0 :(得分:9)
我尝试使用g ++ 6.1.0编译此代码。我完全不确定细节,但我认为memmove
调用不是由编译器直接生成的;相反,它在代码中实现<vector>
。
当我使用
预处理代码时/o/apps/gcc-6.1.0/bin/g++ -E -std=c++14 c.cpp
我看到__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.
我认为发生的事情是,为复制向量而调用的代码更适用于可以重叠的副本(例如对std::move
(?)的调用)。
(我尚未确认汇编列表中显示的memmove
来电与__builtin_memmove
中的stl_algobase.h
来电相对应。我邀请其他任何人跟进这一点。)
根据实施情况,memmove()
相对于memcpy()
可能有一些开销,但差别很小。可能只是不值得为不能重叠的副本创建特例代码。
答案 1 :(得分:8)
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;
我故意将变量放在文件范围内,以防止优化它们而不必处理volatile
语义。
首先让我们试试:
std::copy(&a[0], &a[5], &b[0]);
-O3 -fdump-tree-optimized
这将成为:
__builtin_memcpy (&b[0], &a[0], 20);
单步通过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 }
等待使用memmove
?!好吧,让我们继续。
怎么样:
std::copy(&a[0], &a[5], v2.begin());
好的,让我们memmove
:
int * _2;
<bb 2>:
_2 = MEM[(int * const &)&v2];
__builtin_memmove (_2, &a[0], 20);
如果我们执行-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
啊,我明白了。它使用C库提供的优化memcpy
例程。但等一下,这没有意义。 memmove
和memcpy
是两回事!
查看此例程的source code,我们看到几乎没有检查:
85 #ifndef USE_AS_MEMMOVE
86 cmp %dil, %sil
87 jle L(copy_backward)
88 #endif
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)
但如果我们将&v2[0]
替换为*v2.data()
,则不会调用GLIBC的memmove
。那是怎么回事?
好v2[0]
和v2.begin()
返回迭代器,而v2.data()
返回指向内存的直接指针。我认为这可以防止GCC将memmove
优化为memcpy
。 [citation needed]
答案 2 :(得分:5)
在这种情况下,实施者使用memmove
超过memcpy
的理由可能存在缺陷。
memmove
与memcpy
的不同之处在于memmove
中的内存区域可能重叠(因此概念上效率稍低)。
memcpy
具有两个内存区域不得重叠的约束。
对于向量的复制构造函数,内存区域永远不会重叠,因此可以认为memcpy
是更好的选择。