就性能而言,使用std::memcpy()还是std::copy()更好?

是如下所示使用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]);
157446 次浏览

我所知道的所有编译器都会在适当的时候将简单的std::copy替换为memcpy,或者更好的是,将副本向量化,这样它会比memcpy更快。

在任何情况下:侧写和找出自己。不同的编译器会做不同的事情,它很可能不会完全按照你的要求去做。

参见这篇关于编译器优化的演讲 (pdf)。

下面是一个POD类型的简单std::copyGCC做什么

#include <algorithm>


struct foo
{
int x, y;
};


void bar(foo* a, foo* b, size_t n)
{
std::copy(a, a + n, b);
}

下面是反汇编(只有-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

如果将函数签名更改为

void bar(foo* __restrict a, foo* __restrict b, size_t n)

那么memmove就会变成memcpy,从而略微提高性能。注意memcpy本身将被大量向量化。

理论上,memcpy可能具有轻微的听不清无穷小,性能优势,只是因为它没有与std::copy相同的要求。从memcpy的手册页:

为避免溢出,设置 由目标指向的数组 而源参数,应在 最小num字节,<强>,不应该 Overlap (用于内存重叠 块,memmove是一个更安全的方法)

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

因此,由于std::copy有一些不同的要求,理论上它应该是(特别强调)更慢,因为它可能会检查重叠的c数组,或者委托复制c数组给需要执行检查的memmove。但在实践中,您(和大多数分析人员)甚至可能察觉不到任何差异。

当然,如果你不使用豆荚,你不能还是会使用memcpy

我的原则很简单。如果你正在使用c++,更喜欢c++库而不是C:)

始终使用std::copy,因为memcpy仅限于c风格的POD结构,如果目标实际上是POD,编译器可能会用memcpy替换对std::copy的调用。

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

我将在这里反对普遍的智慧,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%

std::copy比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%

std::copy比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%

std::copy比memcpy的速度增加了0.72%

总之,使用std::copy似乎没有性能损失。事实上,这似乎是一种性能增益。

结果说明

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

首先,只要打开内联优化,我不期望它在任何实现中都变慢。所有编译器都内联;它可能是最重要的优化,因为它支持许多其他优化。std::copy可以(我怀疑所有现实世界的实现都可以)检测到参数是可简单复制的,并且内存是按顺序布局的。这意味着在最坏的情况下,当memcpy合法时,std::copy的性能应该不会更差。继承memcpystd::copy的简单实现应该满足编译器的标准“在优化速度或大小时始终内联此”。

然而,std::copy也保留了更多的信息。当调用std::copy时,函数保持类型不变。memcpy操作于void *,后者会丢弃几乎所有有用的信息。例如,如果传入一个std::uint64_t数组,编译器或库实现者可能能够利用std::copy的64位对齐,但使用memcpy可能更难做到这一点。很多算法的实现都是这样的首先处理范围开头的未对齐部分,然后是对齐部分,最后是未对齐部分。如果保证这些都是对齐的,那么代码就会变得更简单更快,处理器中的分支预测器也更容易得到正确的结果。

过早优化?

std::copy处于一个有趣的位置。我希望它永远不会比memcpy慢,有时用任何现代优化编译器都会更快。而且,任何你可以memcpy的东西,你也可以std::copymemcpy不允许缓冲区中的任何重叠,而std::copy支持一个方向的重叠(std::copy_backward支持另一个方向的重叠)。memcpy只适用于指针,std::copy适用于任何迭代器(std::mapmemcpy0、memcpy1或我自己的自定义类型)。换句话说,当你需要复制数据块时,你应该只使用std::copy

分析显示:std::copy()总是和memcpy()一样快或更快的语句是假的。

我的系统:

HP-Compaq-dx7500-Microtower 3.13.0-24-generic #47-Ubuntu SMP周五5月2日 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux.

gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

代码(语言:c++):

    const uint32_t arr_size = (1080 * 720 * 3); //HD image in rgb24
const uint32_t iterations = 100000;
uint8_t arr1[arr_size];
uint8_t arr2[arr_size];
std::vector<uint8_t> v;


main(){
{
DPROFILE;
memcpy(arr1, arr2, sizeof(arr1));
printf("memcpy()\n");
}


v.reserve(sizeof(arr1));
{
DPROFILE;
std::copy(arr1, arr1 + sizeof(arr1), v.begin());
printf("std::copy()\n");
}


{
time_t t = time(NULL);
for(uint32_t i = 0; i < iterations; ++i)
memcpy(arr1, arr2, sizeof(arr1));
printf("memcpy()    elapsed %d s\n", time(NULL) - t);
}


{
time_t t = time(NULL);
for(uint32_t i = 0; i < iterations; ++i)
std::copy(arr1, arr1 + sizeof(arr1), v.begin());
printf("std::copy() elapsed %d s\n", time(NULL) - t);
}
}

g++ -O0 -o test_stdcopy test_stdcopy.cpp

memcpy() profile: main:21: now:1422969084:04859 elapsed:2650 us
Std::copy() profile: main:27: now:1422969084:04862 elapsed:2745 us
Memcpy()消失44秒std::copy()消失45秒

g++ -O3 -o test_stdcopy test_stdcopy.cpp

memcpy() profile: main:21: now:1422969601:04939 elapsed:2385 us
Std::copy()配置文件:main:28: now:1422969601:04941 elapsed:2690 us
Memcpy()消失了27秒std::copy()消失了43秒

红色警报指出,代码使用memcpy从数组到数组和std::复制从数组到向量。这可能是memcpy更快的原因。

因为有

v.reserve (sizeof (arr1));

复制到向量或数组时应该没有区别。

代码在这两种情况下都固定使用数组。Memcpy更快:

{
time_t t = time(NULL);
for(uint32_t i = 0; i < iterations; ++i)
memcpy(arr1, arr2, sizeof(arr1));
printf("memcpy()    elapsed %ld s\n", time(NULL) - t);
}


{
time_t t = time(NULL);
for(uint32_t i = 0; i < iterations; ++i)
std::copy(arr1, arr1 + sizeof(arr1), arr2);
printf("std::copy() elapsed %ld s\n", time(NULL) - t);
}


memcpy()    elapsed 44 s
std::copy() elapsed 48 s

如果你真的需要最大的复制性能(你可能不需要),两者都不要用

有一个很多可以做来优化内存复制-甚至更多,如果你愿意使用多个线程/核。例如:

什么's缺失/次优在这个memcpy实现?< / >

问题和一些答案都建议了实现或实现的链接。

只是一个小小的补充:memcpy()std::copy()之间的速度差异可能会有很大的差异,这取决于是否启用或禁用优化。在g++ 6.2.0和没有优化的情况下,memcpy()明显胜出:

Benchmark             Time           CPU Iterations
---------------------------------------------------
bm_memcpy            17 ns         17 ns   40867738
bm_stdcopy           62 ns         62 ns   11176219
bm_stdcopy_n         72 ns         72 ns    9481749

当启用优化时(-O3),一切看起来几乎相同:

Benchmark             Time           CPU Iterations
---------------------------------------------------
bm_memcpy             3 ns          3 ns  274527617
bm_stdcopy            3 ns          3 ns  272663990
bm_stdcopy_n          3 ns          3 ns  274732792

数组越大,效果就越不明显,但即使在N=1000,当不启用优化时,memcpy()的速度也大约是之前的两倍。

源代码(需要谷歌基准):

#include <string.h>
#include <algorithm>
#include <vector>
#include <benchmark/benchmark.h>


constexpr int N = 10;


void bm_memcpy(benchmark::State& state)
{
std::vector<int> a(N);
std::vector<int> r(N);


while (state.KeepRunning())
{
memcpy(r.data(), a.data(), N * sizeof(int));
}
}


void bm_stdcopy(benchmark::State& state)
{
std::vector<int> a(N);
std::vector<int> r(N);


while (state.KeepRunning())
{
std::copy(a.begin(), a.end(), r.begin());
}
}


void bm_stdcopy_n(benchmark::State& state)
{
std::vector<int> a(N);
std::vector<int> r(N);


while (state.KeepRunning())
{
std::copy_n(a.begin(), N, r.begin());
}
}


BENCHMARK(bm_memcpy);
BENCHMARK(bm_stdcopy);
BENCHMARK(bm_stdcopy_n);


BENCHMARK_MAIN()


/* EOF */