20

C++ 中是否有更快的 memcpy() 替代方法?

4

8 回答 8

21

首先,忠告。假设编写您的标准库的人并不愚蠢。如果有更快的方法来实现一个通用的 memcpy,他们会做到的。

其次,是的,有更好的选择。

  • 在 C++ 中,使用该std::copy函数。它做同样的事情,但它 1)更安全,2)在某些情况下可能更快。它是一个模板,这意味着它可以专门用于特定类型,使其可能比一般的 C memcpy 更快。
  • 或者,您可以利用您对特定情况的丰富知识。memcpy 的实现者必须编写它,因此它在每种情况下都表现良好。如果你有关于你需要它的情况的具体信息,你也许可以写一个更快的版本。例如,您需要复制多少内存?它是如何对齐的?这可能允许您为这种特定情况编写更有效的 memcpy。但在大多数其他情况下它不会那么好(如果它会工作的话)
于 2009-07-30T21:51:38.070 回答
20

不太可能。您的编译器/标准库可能会有一个非常高效且量身定制的 memcpy 实现。memcpy 基本上是用于将内存的一部分复制到另一部分的最低 api。

如果您想进一步加快速度,请找到一种不需要任何内存复制的方法。

于 2009-07-30T21:39:39.030 回答
11

优化专家 Agner Fog 发布了优化的内存函数:http ://agner.org/optimize/#asmlib 。虽然它在 GPL 下。

前段时间,Agner 说这些函数应该替换 GCC 内置函数,因为它们要快得多。我不知道从那以后有没有这样做。

于 2009-07-30T22:41:21.603 回答
8

一个非常相似的问题(关于memset())的答案也适用于这里。

它基本上说编译器会根据对象的性质(大小、对齐方式等)为memcpy()/ - 和不同的代码生成一些非常优化的代码。memset()

请记住,只有memcpy()C++ 中的 POD。

于 2009-07-30T21:37:36.960 回答
6

为了找到或编写一个快速的内存复制例程,我们应该了解处理器是如何工作的。

自 Intel Pentium Pro 以来的处理器执行“乱序执行”。如果指令没有依赖关系,它们可能会并行执行许多指令。但这仅是指令仅使用寄存器操作时的情况。如果它们与内存一起操作,则使用额外的 CPU 单元,称为“加载单元”(从内存中读取数据)和“存储单元”(将数据写入内存)。大多数 CPU 有两个加载单元和一个存储单元,即它们可以并行执行两条从内存读取的指令和一条写入内存的指令(同样,如果它们不相互影响)。这些单元的大小通常与最大寄存器大小相同——如果 CPU 有 XMM 寄存器 (SSE)——它是 16 字节,如果它有 YMM 寄存器 (AVX)——它是 32 字节,依此类推。所有读取或写入内存的指令都被转换为微操作(micro-ops),这些微操作(micro-ops)进入公共微操作池,并在那里等待加载和存储单元能够为它们服务。单个加载或存储单元一次只能服务一个微操作,无论它需要加载或存储的数据大小如何,无论是 1 字节还是 32 字节。

因此,最快的内存复制将移入和移出具有最大大小的寄存器。对于支持 AVX 的处理器(但没有 AVX-512),复制内存的最快方法是重复以下序列,循环展开:

vmovdqa     ymm0,ymmword ptr [rcx]
vmovdqa     ymm1,ymmword ptr [rcx+20h]
vmovdqa     ymmword ptr [rdx],ymm0
vmovdqa     ymmword ptr [rdx+20h],ymm1

hplbsh 之前发布的 Google 代码不是很好,因为它们在开始写回数据之前使用所有 8 个 xmm 寄存器来保存数据,而这并不是必需的——因为我们只有两个加载单元和一个存储单元。所以只有两个寄存器可以提供最好的结果。使用这么多寄存器绝不会提高性能。

内存复制例程也可能使用一些“高级”技术,例如“预取”来指示处理器提前将内存加载到缓存中和“非临时写入”(如果您正在复制非常大的内存块并且不需要数据从输出缓冲区立即读取),对齐与未对齐写入等。

自 2013 年以来发布的现代处理器,如果它们的 CPUID 中有 ERMS 位,则具有所谓的“增强的 rep movsb”,因此对于大内存副本,可能会使用“rep movsb” - 副本将非常快,甚至比使用 ymm 寄存器要快,并且可以正常使用缓存。然而,这条指令的启动成本非常高——大约 35 个周期,所以它只在大内存块上支付(然而,这可能会在未来的处理器中改变)。有关“rep movsb”的更多信息,请参阅https://stackoverflow.com/a/43845229/6910868上的“相对性能说明”部分,另请参阅https://stackoverflow.com/a/43837564/6910868 。

我希望您现在可以更轻松地选择或编写适合您的案例所需的最佳内存复制例程。

您甚至可以保留标准的 memcpy/memmove,但根据需要获取自己的特殊 largememcpy()。

于 2017-05-10T00:02:59.803 回答
2

我不确定使用默认的 memcpy 总是最好的选择。我看过的大多数 memcpy 实现倾向于在开始时尝试对齐数据,然后进行对齐的副本。如果数据已经对齐,或者非常小,那么这是在浪费时间。

有时,有专门的字副本、半字副本、字节副本 memcpy 是有益的,只要它对缓存没有太大的负面影响。

此外,您可能希望更好地控制实际分配算法。在游戏行业中,人们编写自己的内存分配例程是非常普遍的,无论工具链开发人员首先花费了多少精力来开发它。我见过的游戏几乎总是倾向于使用Doug Lea 的 Malloc

不过一般来说,您会浪费时间尝试优化 memcpy,因为毫无疑问,您的应用程序中会有很多更容易的代码来加速。

于 2009-07-30T22:07:10.020 回答
1

取决于你想要做什么......如果它是一个足够大的memcpy,并且你只是稀疏地写入副本,那么使用 MMAP_PRIVATE 创建一个写时复制映射的 mmap 可能会更快。

于 2009-07-30T21:43:11.330 回答
1

根据您的平台,可能会有特定的用例,例如,如果您知道源和目标与缓存线对齐,并且大小是缓存线大小的整数倍。一般来说,大多数编译器都会为 memcpy 生成相当优化的代码。

于 2009-07-30T21:43:38.973 回答