我在C中进行图像处理,需要在内存周围复制大块数据 - 源和目标永远不会重叠。
使用GCC(其中SSE,SSE2但不是SSE3可用)在x86平台上执行此操作的绝对最快方式是什么?
我希望解决方案可以是汇编还是使用GCC内在函数?
我找到了以下链接,但不知道这是否是最佳方式(作者还说它有一些错误):http://coding.derkeiler.com/Archive/Assembler/comp.lang.asm.x86/2006-02/msg00123.html
编辑:请注意,副本是必要的,我无法复制数据(我可以解释原因,但我会饶恕你的解释:))
答案 0 :(得分:40)
由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)并删除其特殊情况处理。有可能进一步优化!
答案 1 :(得分:6)
在-O1
或更高级别的任何优化级别,GCC将使用memcpy
等函数的内置定义 - 使用正确的-march
参数(-march=pentium4
作为功能集你提到它应该生成非常优化的体系结构内联代码。
我会对它进行基准测试,看看会发生什么。
答案 2 :(得分:6)
由hapalibashi发布的SSE代码是要走的路。
如果您需要更高的性能,并且不要回避编写设备驱动程序的漫长而曲折的道路:现在所有重要的平台都有一个DMA控制器,能够更快速地并行执行复制作业到CPU代码可以做。
这涉及到写一个司机。由于存在安全风险,我所知道的大型操作系统都没有向用户端公开此功能。
然而,它可能是值得的(如果你需要性能),因为地球上没有任何代码可以胜过设计用于完成这项工作的硬件。
答案 3 :(得分:6)
这个问题现在已经有四年了,我有点惊讶没人提到内存带宽。 CPU-Z报告我的机器有PC3-10700 RAM。 RAM具有10700 MBytes / sec的峰值带宽(也称为传输速率,吞吐量等)。我机器中的CPU是i5-2430M CPU,峰值turbo频率为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真正可以做的事情。准确地做这件事非常繁琐。我做了一个简单的应用程序,将随机数写入数组,将它们存储到另一个数组,然后检查复制的数据。我逐步调试了调试器中的代码,以确保聪明的编译器没有删除副本。改变数组的大小会改变缓存性能 - 小数组适合缓存,大数据则不太适合缓存。我得到了以下结果:
显然,我的CPU每个周期可以读写超过32位,因为16000比我理论上计算的12000多。这意味着CPU比我想象的更不容易出现瓶颈。我使用Visual Studio 2005,并进入标准的memcpy实现,我可以看到它在我的机器上使用movqda指令。我想这可以在每个周期读写64位。
好的代码hapalibashi发布在我的机器上达到4200 MB /秒 - 比VS 2005实施快约40%。我想它更快,因为它使用预取指令来提高缓存性能。
总之,在CPU上运行的代码不是瓶颈,调整代码只会有很小的改进。
答案 4 :(得分:3)
如果特定于英特尔处理器,您可能会从IPP中受益。如果您知道它将与Nvidia GPU一起运行,也许您可以使用CUDA - 在这两种情况下,看起来比优化memcpy()更好 - 它们提供了在更高级别上改进算法的机会。但它们都依赖于特定的硬件。
答案 5 :(得分:2)
如果您使用的是Windows,请使用DirectX API,这些API具有特定的GPU优化的图形处理例程(它的速度有多快?您的CPU未加载。请执行其他操作当GPU咀嚼它时。)
如果您想与操作系统无关,请尝试OpenGL。
不要使用汇编程序,因为你很可能会惨遭失败,超过10年+熟练的图书馆制作软件工程师。
答案 6 :(得分:-1)
如果您可以使用DMA引擎,没有比这更快的事了。