在性能方面使用std :: memcpy()或std :: copy()会更好吗?

use*_*670 154 c++ optimization performance

memcpy如下所示使用它是否更好,或者std::copy()在性能方面更好用?为什么?

char *bits = NULL;
...

bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
    cout << "ERROR Not enough memory.\n";
    exit(1);
}

memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);
Run Code Online (Sandbox Code Playgroud)

Dav*_*one 191

我将违背这里的一般智慧,这std::copy将带来轻微的,几乎难以察觉的性能损失.我刚做了一个测试,发现这是不真实的:我确实注意到了性能差异.然而,获胜者是std::copy.

我写了一个C++ SHA-2实现.在我的测试中,我使用所有四个SHA-2版本(224,256,384,512)散列5个字符串,并且我循环300次.我使用Boost.timer测量时间.300循环计数器足以完全稳定我的结果.我每次运行测试5次,在memcpy版本和std::copy版本之间交替.我的代码利用尽可能大的块来抓取数据(许多其他实现使用char/ 操作char *,而我使用T/ 操作T *(其中T是用户实现中具有正确溢出行为的最大类型),因此快速访问内存我可以使用的最大类型是我的算法性能的核心.这些是我的结果:

完成SHA-2测试运行的时间(以秒为单位)

std::copy   memcpy  % increase
6.11        6.29    2.86%
6.09        6.28    3.03%
6.10        6.29    3.02%
6.08        6.27    3.03%
6.08        6.27    3.03%
Run Code Online (Sandbox Code Playgroud)

std :: copy over memcpy的平均速度增加:2.99%

我的编译器是Fedora 16 x86_64上的gcc 4.6.3.我的优化标志是-Ofast -march=native -funsafe-loop-optimizations.

我的SHA-2实现的代码.

我决定对我的MD5实现进行测试.结果不太稳定,所以我决定进行10次运行.然而,在我的第一次尝试之后,我得到的结果在一次运行到另一次运行之间变化很大,所以我猜测正在进行某种操作系统活动.我决定重新开始.

相同的编译器设置和标志.只有一个版本的MD5,它比SHA-2更快,所以我在一组类似的5个测试字符串上做了3000个循环.

这是我最后的10个结果:

完成MD5测试运行的时间(以秒为单位)

std::copy   memcpy      % difference
5.52        5.56        +0.72%
5.56        5.55        -0.18%
5.57        5.53        -0.72%
5.57        5.52        -0.91%
5.56        5.57        +0.18%
5.56        5.57        +0.18%
5.56        5.53        -0.54%
5.53        5.57        +0.72%
5.59        5.57        -0.36%
5.57        5.56        -0.18%
Run Code Online (Sandbox Code Playgroud)

std :: copy over memcpy的平均速度下降:0.11%

我MD5实现的代码

这些结果表明在我的SHA-2测试中使用的std :: copy有一些优化,这些优化std::copy在我的MD5测试中无法使用.在SHA-2测试中,两个数组都是在调用std::copy/ 的相同函数中创建的memcpy.在我的MD5测试中,其中一个数组作为函数参数传递给函数.

我做了一些测试,看看我能做些什么才能让它std::copy再次变得更快.答案结果很简单:打开链接时间优化.这些是我打开LTO的结果(选项-flto在gcc中):

使用-flto完成MD5测试运行的时间(以秒为单位)

std::copy   memcpy      % difference
5.54        5.57        +0.54%
5.50        5.53        +0.54%
5.54        5.58        +0.72%
5.50        5.57        +1.26%
5.54        5.58        +0.72%
5.54        5.57        +0.54%
5.54        5.56        +0.36%
5.54        5.58        +0.72%
5.51        5.58        +1.25%
5.54        5.57        +0.54%
Run Code Online (Sandbox Code Playgroud)

std :: copy over memcpy的平均速度增加:0.72%

总之,使用时似乎没有性能损失std::copy.实际上,似乎有性能提升.

结果说明

那么为什么可以std::copy提升性能呢?

首先,我认为只要打开内联优化,任何实现都不会慢.所有编译器都积极地内联; 它可能是最重要的优化,因为它可以实现许多其他优化.std::copy可以(并且我怀疑所有现实世界的实现都会)检测到参数是可以轻易复制的,并且内存按顺序排列.这意味着在最坏的情况下,何时memcpy合法,std::copy应该表现不差.在简单的实现的std::copy可推迟到memcpy应符合"优化速度或大小时,总是内联这个"编译器的标准.

但是,std::copy也保留了更多的信息.当您调用时std::copy,该函数会保持类型不变.memcpy进行操作void *,丢弃几乎所有有用的信息.例如,如果我传入一个数组std::uint64_t,编译器或库实现者可能能够利用64位对齐std::copy,但可能更难以这样做memcpy.像这样的算法的许多实现通过首先处理范围开始处的未对准部分,然后是对齐部分,然后是末端处的未对准部分来工作.如果保证全部对齐,则代码变得更简单,更快速,并且处理器中的分支预测器更容易正确.

过早优化?

std::copy处于一个有趣的位置.我希望它永远不会比memcpy任何现代优化编译器慢,有时甚至更快.此外,任何事情,你可以memcpy,可以std::copy.memcpy不允许缓冲区中的任何重叠,而std::copy支持在一个方向上重叠(std::copy_backward对于另一个重叠方向).memcpy只适用于指针,std::copy在任何迭代器的工作原理(std::map,std::vector,std::deque,或者我自己的自定义类型).换句话说,您应该只std::copy在需要复制数据块时使用.

  • 我想强调的是,这并不意味着`std :: copy`比`memcpy'快2.99%或0.72%或-0.11%,这些时间是整个程序执行的.但是,我通常认为实际代码中的基准测试比假代码中的基准测试更有用.我的整个程序在执行速度上有所改变.仅仅两个复制方案的实际效果将比单独显示的更大,但这表明它们在实际代码中可以有可测量的差异. (32认同)
  • 非常翔实的分析.Re _在memcpy上的std :: copy速度总平均下降:0.11%_,虽然数字正确,但结果没有统计学意义.平均值差异的95%置信区间为(-0.013s,0.025),其中包括零.正如您所指出的,其他来源和您的数据存在差异,您可能会说性能是相同的.作为参考,其他两个结果具有统计显着性 - 你看到这种极端偶然性的差异的概率大约是1亿分之一(第一)和1分之二(最后). (6认同)
  • @ ST3:我想在最坏的情况下,`std :: copy`是一个简单的内联函数,只要它合法就调用`memcpy`.基本内联将消除任何负面的性能差异.我将更新帖子,并解释为什么std :: copy可能更快. (3认同)
  • 我想不同意你的发现,但结果是结果:/.然而有一个问题(我知道这是很久以前你不记得研究,所以只是评论你的想法),你可能没有考虑汇编代码; (2认同)
  • 在我看来,`memcpy`和`std :: copy`有不同的实现,因此在某些情况下,编译器将周围的代码和实际的内存复制代码优化为一个完整的代码段.换句话说_sometimes_一个比另一个更好,甚至换句话说,决定使用哪个是过早的甚至是愚蠢的优化,因为在每种情况下你都要做新的研究,更重要的是,程序通常是开发的,所以在一些微小的变化功能优于其他功能可能会丢失. (2认同)
  • 这与我在模板下看到的GCC相反.使用std :: copy over memset,代码减慢了5%,但这可能与std :: copy生成的附加代码有关,以及如何在模板使用中优化,而不是代码本身的速度. (2认同)
  • 您的体系结构可能具有一次复制64,128或256位对象的指令.使用`memcpy`,要利用这一点,必须有一个序言和一个epliogue,它可以逐字节地复制任何额外的尾随数据.使用`std :: copy`,因为维护了类型,所以有一些不丢失的对齐保证.从理论上讲,优化器也可以将其视为内联的一部分,在这一点上没有区别. (2认同)

Pet*_*der 77

我所知道的所有编译器都会在适当std::copymemcpy时候替换一个简单的,或者更好的是,将副本矢量化,使其比a更快memcpy.

在任何情况下:简介并找出自己.不同的编译器会做不同的事情,很有可能它不会完全按照你的要求做.

请参阅有关编译器优化的演示文稿(pdf).

以下是GCC为简单std::copy的POD类型所做事情.

#include <algorithm>

struct foo
{
  int x, y;    
};

void bar(foo* a, foo* b, size_t n)
{
  std::copy(a, a + n, b);
}
Run Code Online (Sandbox Code Playgroud)

这是反汇编(仅有-O优化),显示对以下内容的调用memmove:

bar(foo*, foo*, unsigned long):
    salq    $3, %rdx
    sarq    $3, %rdx
    testq   %rdx, %rdx
    je  .L5
    subq    $8, %rsp
    movq    %rsi, %rax
    salq    $3, %rdx
    movq    %rdi, %rsi
    movq    %rax, %rdi
    call    memmove
    addq    $8, %rsp
.L5:
    rep
    ret
Run Code Online (Sandbox Code Playgroud)

如果您将功能签名更改为

void bar(foo* __restrict a, foo* __restrict b, size_t n)
Run Code Online (Sandbox Code Playgroud)

然后memmove成为一个memcpy稍微改善的性能.请注意,memcpy它本身将被大量矢量化.

  • @Konrad,你说的没错.但是'memmove`应该不会更快 - 相反,它应该更慢,因为它必须考虑两个数据范围重叠的可能性.我认为`std :: copy`允许重叠数据,所以它必须调用`memmove`. (5认同)
  • 我怎样才能进行分析。使用什么工具(在 Windows 和 Linux 中)? (2认同)
  • @Konrad:如果memmove总是比memcpy快,那么memcpy会调用memmove.什么std :: copy实际上可以调度到(如果有的话)是实现定义的,所以在不提及实现的情况下提及细节是没有用的. (2认同)

Pup*_*ppy 23

始终使用std::copy,因为memcpy仅限于C-POD风格结构,编译器可能会取代调用std::copymemcpy目标是否实际上POD.

另外,std::copy可以与许多迭代器类型一起使用,而不仅仅是指针.std::copy更灵活,没有性能损失,是明显的赢家.

  • 您不是要复制迭代器,而是复制两个迭代器定义的范围.例如,`std :: copy(container.begin(),container.end(),destination);`将`container`的内容(`begin`和`end`之间的所有内容)复制到` destination`.`std :: copy`不需要像`&*container.begin()`或`&container.back()+ 1`这样的恶作剧. (3认同)

Cha*_*via 17

理论上,memcpy可能具有轻微的,不易察觉的,无穷小的性能优势,仅仅因为它没有相同的要求std::copy.从手册页memcpy:

为避免溢出,目标和源参数指向的数组大小应至少为num个字节,并且不应重叠(对于重叠的内存块,memmove是一种更安全的方法).

换句话说,memcpy可以忽略重叠数据的可能性.(将重叠数组传递给memcpy未定义的行为.)因此memcpy不需要显式检查此条件,而std::copy只要OutputIterator参数不在源范围内就可以使用.请注意,这是一样的话说,来源范围和目标范围不能重叠.

因此,由于std::copy有一些不同的要求,理论上它应该稍微(略微强调略微)较慢,因为它可能会检查重叠的C数组,或者委托复制C数组memmove,这需要执行校验.但在实践中,你(和大多数剖析器)可能甚至都不会发现任何差异.

当然,如果您不使用POD,memcpy无论如何都无法使用.

  • 这对于`std :: copy <char>`来说是正确的.但是`std :: copy <int>`可以假设它的输入是int-aligned.这将产生更大的差异,因为它会影响每个元素.重叠是一次性检查. (7认同)
  • @MSalters,是的,但是大多数`memcpy`的实现我已经看过检查对齐并尝试复制字而不是逐字节. (2认同)
  • 可以提出一个相反的论点:通过“ memcpy”接口时,它将丢失对齐信息。因此,`memcpy`必须在运行时进行对齐检查,以处理未对齐的开始和结束。这些支票可能很便宜,但不是免费的。而`std :: copy`可以避免这些检查并进行向量化。同样,编译器可以证明源数组和目标数组不重叠,并且可以再次向量化,而无需用户在“ memcpy”和“ memmove”之间进行选择。 (2认同)

Umm*_*mma 11

我的规则很简单.如果你使用C++更喜欢C++库而不是C :)

  • C++明确设计为允许使用C库.这不是一个意外.在C++中使用std :: copy比使用memcpy通常更好,但这与哪个是C无关,而这种参数通常是错误的方法. (37认同)
  • @FredNurk通常你想要避免C的弱区域,其中C++提供了更安全的替代方案. (2认同)
  • @underscore_d `std::copy` 更安全,因为它会正确复制传递的数据,以防它们不是 POD 类型。`memcpy` 会愉快地将 `std::string` 对象逐字节复制到新的表示形式。 (2认同)