就性能而言,使用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
应该不会更糟。 按照memcpy
的std::copy
的简单实现应该符合你的编译器的标准:“在优化速度或者大小时总是内联”。
但是, std::copy
也保留了更多的信息。 当你调用std::copy
,函数保持types不变。 memcpy
在void *
上运行,这会丢弃几乎所有有用的信息。 例如,如果我传入一个std::uint64_t
数组,编译器或库实现者可能能够利用std::copy
的64位alignment方式,但memcpy
可能会更困难。 像这样的algorithm的许多实现首先在范围开始处的未alignment部分,然后是alignment部分,然后是未结束部分。 如果它们都保证一致,那么代码变得更简单和更快,并且更容易使处理器中的分支预测器正确。
过早优化?
std::copy
是一个有趣的位置。 我希望它永远不会比memcpy
慢,有时使用任何现代优化编译器速度更快。 而且,任何可以memcpy
东西,都可以std::copy
。 memcpy
不允许在缓冲区中有任何重叠,而std::copy
支持一个方向上的重叠(对于另一个重叠方向, std::copy_backward
)。 memcpy
只能用于指针, std::copy
可以在任何迭代器(std :: map,std :: vector,std :: deque或我自己的自定义types)上工作。 换句话说,当你需要复制数据块时,你应该使用std::copy
。
我所知道的所有编译器会在适当的时候用memcpy
replace一个简单的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,编译器可能会用memcpy
replace对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 sg ++ -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