我要反对这里的普遍智慧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 *
(where 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 实现的代码。 https://bitbucket.org/davidstone/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 实现的代码 https://bitbucket.org/davidstone/md5
这些结果表明 std::copy 在我的 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 相对于 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
当您需要复制大量数据时。