在这里,我将违背通常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次运行。但是,经过最初的几次尝试后,我得到的结果从一次运行到下一次运行有很大的不同,所以我猜测正在发生某种OS活动。我决定重新开始。
相同的编译器设置和标志。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的结果(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
在需要复制数据块时使用。
char
可以签名或不签名,具体取决于实现方式。如果字节数可以大于等于128,则unsigned char
用于您的字节数组。((int *)
演员阵容也会更安全(unsigned int *)
。)