C++ 为什么gcc生成memmove而不是memcpy来复制std::vector<>;?
在gcc 5.3中,以下示例中的两个函数都生成对C++ 为什么gcc生成memmove而不是memcpy来复制std::vector<>;?,c++,performance,gcc,assembly,c++14,C++,Performance,Gcc,Assembly,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 b
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);
}
#包括
int黑洞(const std::vector&);
int copy_vec1(const std::vector&v1){
const std::vector v2{v1.begin(),v1.end()};
返回黑洞(v2);
}
int copy_vec2(const std::vector&v1){
常数自动v2=v1;
返回黑洞(v2);
}
在这种情况下,实施者在
memcpy
上使用memmove
的基本原理可能有缺陷
memmove
与memcpy
的不同之处在于memmove
中的内存区域可能会重叠(因此在概念上效率稍低)
memcpy
具有两个内存区域不得重叠的约束
对于向量的复制构造函数,内存区域永远不会重叠,因此可以认为
memcpy
将是更好的选择。我尝试使用g++6.1.0编译此代码。我一点也不确定细节,但我认为memmove
调用不是由编译器直接生成的;而是在实现
的代码中
当我使用
/o/apps/gcc-6.1.0/bin/g++ -E -std=c++14 c.cpp
我看到两个对\u builtin\u memmove
的调用,都来自../include/c++/6.1.0/bits/stl\u 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
调用是否与stl\u algobase.h
中的\uuuu builtin\u memmove
调用相对应。我邀请其他人跟进这一点。)
根据实现的不同,
memmove()
相对于memcpy()
,可能会有一些开销,但差别很小。为不能重叠的副本创建特殊案例代码可能不值得;DRGCC不会优化对memmove
内部std::copy
的调用。当使用两个C样式的数组时,它确实如此。将&v2[0]
替换为*v2.data()
可以将其优化为memcpy
您的示例非常嘈杂,因此让我们将其删除:
#include <vector>
#include <algorithm>
int a[5];
int b[5];
std::vector<int> v2;
随着-O3-fdump树的优化
这将成为:
__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
:
int * _2;
<bb 2>:
_2 = MEM[(int * const &)&v2];
__builtin_memmove (_2, &a[0], 20);
55 mov %rdi, %rax
(gdb) s
61 cmp %rsi, %rdi
(gdb) s
62 jb L(copy_forward)
(gdb) s
63 je L(write_0bytes)
啊,我明白了。它使用C库提供的优化memcpy
例程。但是等一下,这没有意义memmove
和memcpy
是两件不同的事情
查看此例行程序的检查结果,我们会看到一些零星检查:
85 #ifndef USE_AS_MEMMOVE
86 cmp %dil, %sil
87 jle L(copy_backward)
88 #endif
GDB确认将其视为memmove
:
int * _2;
<bb 2>:
_2 = MEM[(int * const &)&v2];
__builtin_memmove (_2, &a[0], 20);
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.data()
替换&v2[0]
,它就不会调用GLIBC的memmove
。发生了什么事
好的
v2[0]
和v2.begin()
返回迭代器,而v2.data()
返回指向内存的直接指针。我认为这是出于某种原因,阻止了GCC将memmove
优化为memcpy
[需要引用]我想你的意思是v1.begin(),v1.end()
?@xaxxon如果有原因,那么我会将其添加到我的知识库中。在评论中,STL解释说,从memmove
和memcpy
生成的代码对于MSVC基本相同,因此更容易始终使用前者。gcc也可能如此。@Praetorian:gcc没有自己的memcpy
和memmove
实现;它使用系统库提供的任何内容。glibc是Linux上使用的库实现;查看源代码,似乎memcpy
可能比memmove
更有效。可能会提出一个与缓存相关的解释——但看看我的答案。@KeithThompson是的,我认为“不值得麻烦”是这样概括的。向量复制构造函数不太可能在紧循环中调用,如果是,内存分配成本将超过重叠区域(不可能)情况下可能出现的预测相关暂停。因此,在最坏的情况下,调用memmove的代价是一个冗余比较,这是可以正确预测的。在这种情况下,输入/输出范围如何可能重叠?我们在复制@巴里:不可能。我建议调用的代码也用于范围可能重叠的情况。有趣的事实:x86 gcc将编译memmove
到rep movsd
if。最初只有某些-mtune
设置会内联memcpy/memmove,例如-m32-mtune=intel
(但不是-mtune=haswell
)。但是memcpy()
和memmove()
的存在暗示着一定存在不可忽略的差异,否则只有memmove()
,对吗?@dats:可能会有一些不同(尽管一致性实现可能会对两者使用完全相同的代码)。问题是,是否值得努力找出在每种情况下使用哪一种。我还没有深入研究生成的代码或标题,但我可以想象,在不同情况下共享代码的优势超过了使用memcpy()
的速度优势。如果您正在进行显式调用,并且碰巧知道源和目标没有重叠,请务必使用memcpy()。但是对于v2.data()
我看到的生成代码与v2.begin()
或&v2[0]
完全相同。。。
55 mov %rdi, %rax
(gdb) s
61 cmp %rsi, %rdi
(gdb) s
62 jb L(copy_forward)
(gdb) s
63 je L(write_0bytes)