繁体   English   中英

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

[英]Why does gcc generate a memmove instead of a memcpy for copying a std::vector<>?

使用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);
}

关于Godbolt的例子

我尝试使用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调用对应于stl_algobase.h__builtin_memmove调用。我邀请其他人跟进这一点。)

根据实现, memmove()可能会有一些相对于memcpy()开销,但差别很小。 可能只是不值得为不能重叠的副本创建特例代码。

TL; DR GCC不优化对std::copy内的memmove的调用。 当使用两个C风格的数组时,它确实如此。 *v2.data()替换&v2[0]允许它优化为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例程。 但等一下,这没有意义。 memmovememcpy是两回事!

查看此例程的源代码 ,我们看到几乎没有检查:

  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.data()替换&v2[0] ,它就不会调用GLIBC的memmove 发生什么了?

好吧v2[0]v2.begin()返回迭代器,而v2.data()返回一个指向内存的直接指针。 我认为这可以防止GCC将memmove优化为memcpy [引证需要]

在这种情况下,实现者使用memmove不是memcpy的基本原理可能存在缺陷。

memmove不同于memcpy ,在所述存储器区域memmove可以重叠(并因此它的概念性略效率较低)。

memcpy具有两个内存区域不得重叠的约束。

对于向量的复制构造函数,内存区域永远不会重叠,因此可以认为memcpy是更好的选择。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM