就性能而言,使用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
会有轻微的,几乎察觉不到的性能损失。 我只是做了一个测试,发现这是不真实的:我注意到了性能差异。 但是,获胜者是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 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%
std :: copy over memcpy的总体平均速度下降:0.11%
我的MD5实现代码
这些结果表明,有一些优化是标准::在我的SHA-2测试使用复制std::copy
不能在我的MD5测试使用。 在SHA-2测试中,两个数组都是在与std::copy
/ memcpy
相同的函数中创建的。 在我的MD5测试中,其中一个数组作为函数参数传递给函数。
我做了一些更多的测试,看看我能做些什么来使std::copy
再次更快。 答案很简单:打开链接时间优化。 这些是我打开LTO的结果(在gcc中选项-flto):
用-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 over memcpy的总速度平均增长速度:0.72%
总之,使用std::copy
似乎并没有性能损失。 事实上,似乎有一个性能增益。
结果解释
那么为什么std::copy
会提高性能呢?
首先,我不希望任何实现都会变慢,只要打开内联的优化。 所有编译器积极内联; 它可能是最重要的优化,因为它支持许多其他优化。 std::copy
可以(并且我怀疑所有真实世界的实现都可以)检测到参数是可以复制的,并且内存按顺序排列。 这意味着在最坏的情况下,当memcpy
是合法的, std::copy
应该不会更糟。 遵循memcpy
的std::copy
的简单实现应该符合编译器的标准:“在优化速度或大小时总是内联”。
但是, std::copy
也保留了更多的信息。 当你调用std::copy
,函数保持类型不变。 memcpy
在void *
上运行,它丢弃几乎所有有用的信息。 例如,如果我传入一个std::uint64_t
数组,编译器或库实现者可能能够利用std::copy
的64位对齐方式,但使用memcpy
可能会更困难。 像这样的算法的许多实现首先在范围开始处的未对齐部分,然后是对齐部分,然后是未结束部分。 如果它们都保证一致,那么代码变得更简单和更快,并且更容易使处理器中的分支预测器正确。
过早优化?
std::copy
处于一个有趣的位置。 我希望它永远不会比memcpy
慢,有时使用任何现代优化编译器都会更快。 此外,任何你可以memcpy
,你可以std::copy
。 memcpy
不允许在缓冲区中有任何重叠,而std::copy
支持一个方向上的重叠(对于另一个重叠方向, std::copy_backward
)。 memcpy
只适用于指针, std::copy
适用于任何迭代器( std::map
, std::vector
, std::deque
或我自己的自定义类型)。 换句话说,当你需要复制数据块时,你应该使用std::copy
。
我认识的所有编译器会在适当的时候用memcpy
替换一个简单的std::copy
,甚至更好的是将拷贝向量化,以使它比memcpy
更快。
无论如何:简介并找出你自己。 不同的编译器会做不同的事情,而且很可能它不会完全按照你的要求去做。
请参阅关于编译器优化的演示文稿(pdf)。
以下是GCC为简单的POD类型的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
本身将严重矢量化。
总是使用std::copy
因为memcpy
仅限于C风格的POD结构,如果目标实际上是POD,编译器可能会用memcpy
替换对std::copy
调用。
另外, std::copy
可以用于许多迭代器类型,而不仅仅是指针。 std::copy
对于没有性能损失更加灵活,并且是明显的赢家。
上一篇: Is it better to use std::memcpy() or std::copy() in terms to performance?