Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/143.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么gcc生成memmove而不是memcpy来复制std::vector<>;?_C++_Performance_Gcc_Assembly_C++14 - Fatal编程技术网

C++ 为什么gcc生成memmove而不是memcpy来复制std::vector<>;?

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

在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);
}
#包括
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)