用于图像处理的非常快的memcpy?

ibrsph3r  于 2023-06-05  发布在  其他
关注(0)|答案(8)|浏览(389)

我正在用C语言做图像处理,这需要在内存中复制大量数据-源和目标永远不会重叠。
在x86平台上使用GCC(其中SSE、SSE2但不包括SSE3)执行此操作的绝对最快方法是什么?
我希望解决方案将是在汇编或使用GCC内部?
我找到了下面的链接,但不知道这是否是最好的方法(作者也说它有一些bug):http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html
编辑:注意,复制是必要的,我不能绕过必须复制数据(我可以解释为什么,但我会省去你的解释:))

sbtkgmzw

sbtkgmzw1#

来自William Chan和Google。比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)并去掉其特殊情况处理。可以进一步优化!

h6my8fg2

h6my8fg22#

hapalibashi发布的SSE代码是要走的路。
如果您需要更高的性能,并且不回避编写设备驱动程序的漫长而曲折的道路:现在所有重要的平台都有一个DMA控制器,它能够更快地完成复制工作,并且与CPU代码并行。
这就需要写一个驱动程序。据我所知,没有大型操作系统会因为安全风险而向用户端公开此功能。
然而,这可能是值得的(如果您需要性能),因为地球上没有任何代码可以胜过设计用于完成此类工作的硬件。

o8x7eapl

o8x7eapl3#

这个问题已经有四年了,我有点惊讶还没有人提到内存带宽。CPU-Z报告我的机器有PC 3 -10700 RAM。RAM的峰值带宽(也称为传输速率、吞吐量等)为10700 MB/秒。我的机器中的CPU是i5- 2430 M CPU,峰值turbo频率为3 GHz。
理论上,有了无限快的CPU和我的RAM,memcpy可以达到5300 MB/秒,即10700的一半,因为memcpy必须从RAM读取,然后写入RAM。(编辑:正如v.oddou指出的,这是一个过于简单的近似)。
另一方面,想象我们有无限快的RAM和一个现实的CPU,我们能实现什么?让我们以我的3GHz CPU为例。如果它可以在每个周期进行32位读取和32位写入,则它可以传输3e 9 * 4 =12000 MB/秒。这似乎很容易达到一个现代的CPU。我们已经可以看到,在CPU上运行的代码并不是真正的瓶颈。这是现代机器具有数据缓存的原因之一。
当我们知道数据被缓存时,我们可以通过对memcpy进行基准测试来衡量CPU真正能做什么。准确地做到这一点是很麻烦的。我做了一个简单的应用程序,将随机数写入一个数组,将它们memcpy到另一个数组,然后对复制的数据进行校验和。我在调试器中逐步检查了代码,以确保聪明的编译器没有删除副本。更改阵列大小会改变该高速缓存性能-小阵列适合缓存,大阵列则不适合。我得到了以下结果:

  • 40 KB阵列:16000 MB/秒
  • 400 KB阵列:11000 MB/秒
  • 4000 KB阵列:3100 MB/秒

显然,我的CPU每个周期可以读写超过32位,因为16000比我上面理论计算的12000多。这意味着CPU的瓶颈比我想象的要小。我使用的是VisualStudio 2005,进入标准的memcpy实现,我可以看到它在我的机器上使用了movqda指令。我猜这可以读和写64位每周期。
hapalibashi发布的代码在我的机器上达到了4200 MB/秒-比VS 2005实现快了40%。我猜它更快,因为它使用预取指令来提高缓存性能。
总之,在CPU上运行的代码不是瓶颈,对代码进行调优只会带来很小的改进。

xqnpmsa8

xqnpmsa84#

-O1或更高的任何优化级别上,GCC将使用memcpy等函数的内置定义-使用正确的-march参数(-march=pentium4用于您提到的功能集),它应该生成非常优化的特定于体系结构的内联代码。
我会做个基准测试看看结果如何。

7vhp5slm

7vhp5slm5#

如果是针对英特尔处理器,您可能会受益于IPP。如果你知道它将在Nvidia GPU上运行,也许你可以使用CUDA--在这两种情况下,看起来比优化memcpy()更广泛--它们为在更高层次上改进你的算法提供了机会。然而,它们都依赖于特定的硬件。

ef1yzkbh

ef1yzkbh6#

如果您使用的是Windows,请使用DirectX API,该API具有特定的GPU优化例程,用于图形处理(它能有多快?您的CPU未加载。在GPU咀嚼它的同时做其他事情)。
如果你想成为操作系统不可知论者,试试OpenGL
不要摆弄汇编程序,因为你很可能会惨败,无法超过10年以上熟练的库制作软件工程师。

6vl6ewon

6vl6ewon7#

老问题,但有两件事至今没有人指出:
1.大多数编译器都有自己的memcpy版本;由于memcpy是定义良好的,也是C标准的一部分,编译器不必使用系统库附带的实现,他们可以自由使用自己的实现。正如问题中提到的“intrinsic”,实际上大多数时候你在代码中编写memcpy,实际上你使用的是编译器内部函数,因为这是编译器内部使用的,而不是真实的调用memcpy,因为它甚至可以内联它,从而消除任何函数调用开销。
1.我所知道的大多数memcpy实现在可用时都在内部使用了SSE 2之类的东西,至少好的实现是这样的。VisualStudio 2005中的一个可能没有使用它,但GCC已经使用了很长时间。当然,它们使用什么取决于构建设置。它们将只使用代码将在其上运行的所有CPU可用的指令,因此请确保正确设置架构(例如marchmtune),以及其他标志(例如使得能够支持可选的指令集)。所有这些都会影响编译器在最终二进制文件中为memcpy生成的代码。
所以,不要以为你可以胜过编译器或系统(不同的CPU可能有不同的memcpy实现),基准测试可以证明这一点!除非一个基准测试表明你的手写代码在真实的生活中更快,否则就把它留给编译器和系统,因为它们会适应新的CPU,系统可能会得到更新,自动使你的代码在未来运行得更快,而你必须自己重新优化手写代码,除非你自己发布更新,否则它永远不会变得更快。

6qfn3psc

6qfn3psc8#

如果您可以访问DMA引擎,没有什么会更快。

相关问题