就性能而言,使用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]); 

我将在这里反对一般智慧,std :: copy将会有轻微的,几乎不可察觉的性能损失。 我只是做了一个testing,发现是不真实的:我注意到了一个性能差异。 不过,获胜者是std :: copy。

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

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

 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 over memcpy的总体平均速度提高:2.99%

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

我的SHA-2实现的代码。

我决定对我的MD5实施进行testing。 结果不太稳定,所以我决定做10次运行。 然而,在我的第一次尝试之后,我得到的结果从一次跑到另一次非常不一样,所以我猜测有一些操作系统活动正在进行。 我决定重新开始。

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

这是我最后的10个结果:

时间(以秒为单位)以完成MD5testing的运行

 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 over memcpy的总体平均速度下降:0.11%

代码为我的MD5实现

这些结果表明,有一些优化,std :: copy在我的SHA-2testing中使用std :: copy在我的MD5testing中不能使用。 在SHA-2testing中,两个数组都是在与std :: copy / memcpy相同的函数中创build的。 在我的MD5testing中,其中一个数组作为函数parameter passing给函数。

我做了一些更多的testing,看看我能做些什么来使std :: copy再次更快。 答案结果很简单:打开链接时间优化。 这些是我打开LTO的结果(在gcc中选项-flto):

用-flto完成MD5testing的运行时间(以秒为单位)

 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 over memcpy的速度总计平均增长率:0.72%

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

结果解释

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

首先,我不希望它的执行速度慢,只要打开内联的优化。 所有编译器都积极内联; 它可能是最重要的优化,因为它可以实现许多其他的优化。 std::copy可以(我怀疑所有现实世界的实现)检测到参数是可复制的,并且内存按顺序排列。 这意味着,在最坏的情况下,当memcpy是合法的, std::copy应该不会更糟。 按照memcpystd::copy的简单实现应该符合你的编译器的标准:“在优化速度或者大小时总是内联”。

但是, std::copy也保留了更多的信息。 当你调用std::copy ,函数保持types不变。 memcpyvoid *上运行,这会丢弃几乎所有有用的信息。 例如,如果我传入一个std::uint64_t数组,编译器或库实现者可能能够利用std::copy的64位alignment方式,但memcpy可能会更困难。 像这样的algorithm的许多实现首先在范围开始处的未alignment部分,然后是alignment部分,然后是未结束部分。 如果它们都保证一致,那么代码变得更简单和更快,并且更容易使处理器中的分支预测器正确。

过早优化?

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

我所知道的所有编译器会在适当的时候用memcpyreplace一个简单的std::copy ,或者甚至更好地向量化这个拷贝,使它比memcpy更快。

在任何情况下:configuration文件,找出自己。 不同的编译器会做不同的事情,而且很可能不会完全按照你的要求去做。

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

以下是GCC为PODtypes的简单std::copy 所做的事情 。

 #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本身将严重vector化。

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

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

从理论上讲, memcpy可能有一个轻微的不可察觉的无限小的性能优势,只是因为它没有和std::copy相同的要求。 从memcpy的手册页:

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

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

因此,由于std::copy要求有所不同,理论上它应该稍微一些 (极其重视),因为它可能会检查重叠的C数组,或者将C数组的拷贝委托给memmove ,需要执行检查。 但是在实践中,你(和大多数人)很可能甚至不会察觉到任何差异。

当然,如果你不使用POD ,你不能使用memcpy

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

如果您想要最大的复制性能,请不要使用它们

很多工作可以优化内存复制 – 如果您愿意使用多个线程/内核,则更是如此。 例如参见:

这个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 )时,一切看起来都-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()速度也会快一倍左右。

源代码(需要Google Benchmark):

 #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 */ 

性能分析显示: std::copy()总是和memcpy()一样快,或者更快是错误的。

我的系统:

HP-Compaq-dx7500-Microtower 3.13.0-24-generic#47-Ubuntu SMP Fri May 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 %ds\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 %ds\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 s std :: copy()过了45 s

g ++ -O3 -o test_stdcopy test_stdcopy.cpp

memcpy()profile:main:21:now:1422969601:04939 elapsed:2385 us
std :: copy()profile:main:28:now:1422969601:04941 elapsed:2690 us
memcpy()已过27 s std :: copy()过了43 s

Red Alert指出,代码使用从数组到数组的memcpy和从数组到数组的std :: copy。 这是一个更快的memcpy的原因。

既然有

v.reserve(的sizeof(ARR1));

复制到vector或数组中应该没有区别。

这两个例子的代码是固定使用数组。 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