36

我正在用 C 语言进行图像处理,这需要在内存周围复制大量数据——源和目标永远不会重叠。

使用GCC在 x86 平台上执行此操作的绝对最快方法是什么(其中SSE、 SSE2 但不是 SSE3 可用)?

我希望解决方案是汇编还是使用 GCC 内在函数?

我找到了以下链接,但不知道这是否是最好的方法(作者还说它有一些错误):http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm。 x86/2006-02/msg00123.html

编辑:请注意,副本是必要的,我无法避免不得不复制数据(我可以解释原因,但我会省去你的解释 :))

4

7 回答 7

45

陈伟霆和谷歌提供。比 Microsoft Visual Studio 2005 中的 memcpy 快 30-70%。

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size)
{

  __asm
  {
    mov esi, src;    //src pointer
    mov edi, dest;   //dest pointer

    mov ebx, size;   //ebx is our counter 
    shr ebx, 7;      //divide by 128 (8 * 128bit registers)


    loop_copy:
      prefetchnta 128[ESI]; //SSE2 prefetch
      prefetchnta 160[ESI];
      prefetchnta 192[ESI];
      prefetchnta 224[ESI];

      movdqa xmm0, 0[ESI]; //move data from src to registers
      movdqa xmm1, 16[ESI];
      movdqa xmm2, 32[ESI];
      movdqa xmm3, 48[ESI];
      movdqa xmm4, 64[ESI];
      movdqa xmm5, 80[ESI];
      movdqa xmm6, 96[ESI];
      movdqa xmm7, 112[ESI];

      movntdq 0[EDI], xmm0; //move data from registers to dest
      movntdq 16[EDI], xmm1;
      movntdq 32[EDI], xmm2;
      movntdq 48[EDI], xmm3;
      movntdq 64[EDI], xmm4;
      movntdq 80[EDI], xmm5;
      movntdq 96[EDI], xmm6;
      movntdq 112[EDI], xmm7;

      add esi, 128;
      add edi, 128;
      dec ebx;

      jnz loop_copy; //loop please
    loop_copy_end:
  }
}

您可以根据您的具体情况和您能够做出的任何假设进一步优化它。

您可能还想查看 memcpy 源 (memcpy.asm) 并去掉它的特殊情况处理。有可能进一步优化!

于 2009-11-11T14:08:54.113 回答
9

hapalibashi 发布的 SSE 代码是必经之路。

如果您需要更高的性能并且不要回避编写设备驱动程序的漫长而曲折的道路:现在所有重要的平台都有一个 DMA 控制器,它能够更快地完成复制工作并与 CPU 代码并行能做。

不过,这涉及编写驱动程序。由于存在安全风险,据我所知,没有大型操作系统会将此功能暴露给用户端。

但是,这可能是值得的(如果您需要性能),因为地球上没有任何代码可以胜过旨在完成此类工作的硬件。

于 2009-11-12T17:31:27.937 回答
8

这个问题现在已经有四年了,我有点惊讶没有人提到内存带宽。CPU-Z 报告我的机器有 PC3-10700 RAM。RAM 的峰值带宽(即传输速率、吞吐量等)为 10700 MBytes/sec。我机器中的 CPU 是 i5-2430M CPU,峰值睿频为 3 GHz。

理论上,使用无限快的 CPU 和我的 RAM,memcpy 可以达到5300 MBytes/sec,即 10700 的一半,因为 memcpy 必须先读取然后写入 RAM。(编辑:正如 v.oddou 所指出的,这是一个简单的近似值)。

另一方面,假设我们拥有无限快的 RAM 和真实的 CPU,我们能实现什么?让我们以我的 3 GHz CPU 为例。如果它可以在每个周期进行 32 位读取和 32 位写入,那么它可以传输 3e9 * 4 = 12000 MBytes/sec。对于现代 CPU,这似乎很容易实现。我们已经可以看到,在 CPU 上运行的代码并不是真正的瓶颈。这是现代机器具有数据缓存的原因之一。

当我们知道数据被缓存时,我们可以通过对 memcpy 进行基准测试来衡量 CPU 真正能做什么。准确地做到这一点很繁琐。我做了一个简单的应用程序,将随机数写入一个数组,memcpy 将它们存储到另一个数组,然后校验复制的数据。我逐步检查了调试器中的代码,以确保聪明的编译器没有删除副本。改变数组的大小会改变缓存的性能——小数组适合缓存,大的则不那么适合。我得到以下结果:

  • 40 KB 阵列:16000 MBytes/秒
  • 400 KB 数组:11000 MBytes/秒
  • 4000 KB 数组:3100 MBytes/秒

显然,我的 CPU 每个周期可以读写超过 32 位,因为 16000 比我上面理论上计算的 12000 多。这意味着 CPU 的瓶颈比我想象的要小。我使用了 Visual Studio 2005,并进入标准的 memcpy 实现,我可以看到它在我的机器上使用了 movqda 指令。我想这可以每个周期读写 64 位。

发布的漂亮代码 hapalibashi 在我的机器上实现了 4200 MBytes/sec - 比 VS 2005 实现快 40%。我猜它更快,因为它使用预取指令来提高缓存性能。

总之,在 CPU 上运行的代码不是瓶颈,调整代码只会带来很小的改进。

于 2013-08-15T11:00:18.457 回答
6

在任何优化级别-O1或更高级别,GCC 将对函数使用内置定义,例如memcpy- 使用正确的-march参数(-march=pentium4对于您提到的一组功能),它应该生成非常优化的特定于体系结构的内联代码。

我会对其进行基准测试,看看会发生什么。

于 2009-11-11T21:54:15.893 回答
3

如果特定于 Intel 处理器,您可能会从IPP中受益。如果您知道它将与 Nvidia GPU 一起运行,也许您可​​以使用CUDA——在这两种情况下,看起来比优化 memcpy() 更宽——它们提供了在更高级别改进算法的机会。然而,它们都依赖于特定的硬件。

于 2009-11-11T14:10:02.653 回答
2

如果您在 Windows 上,请使用DirectX API,它具有特定的GPU优化例程用于图形处理(它有多快?您的 CPU 没有加载。在 GPU 咀嚼它时做其他事情)。

如果您想与操作系统无关,请尝试OpenGL

不要摆弄汇编程序,因为你很可能会惨败于超过 10 年以上的精通图书馆制作软件工程师。

于 2009-11-11T14:00:27.233 回答
-1

如果您可以访问 DMA 引擎,那么没有什么比这更快了。

于 2020-11-18T21:27:56.820 回答