我需要分析一个执行大量数组副本的应用程序,所以我最终分析了这个非常简单的函数:
typedef unsigned char UChar;
void copy_mem(UChar *src, UChar *dst, unsigned int len) {
UChar *end = src + len;
while (src < end)
*dst++ = *src++;
}
我正在使用英特尔VTune进行实际分析,从那里我发现使用gcc -O3和“普通”gcc(4.4)进行编译时存在显着差异。
为了理解原因和方法,我得到了两个汇编的汇编输出。
未经优化的版本就是这个:
.L3:
movl 8(%ebp), %eax
movzbl (%eax), %edx
movl 12(%ebp), %eax
movb %dl, (%eax)
addl $1, 12(%ebp)
addl $1, 8(%ebp)
.L2:
movl 8(%ebp), %eax
cmpl -4(%ebp), %eax
jb .L3
leave
所以我看到它首先从* src加载dword并将低位字节放入edx,然后将其存储到* dst并更新指针:简单就足够了。
然后我看到了优化版本,我什么都不懂。
编辑:here有优化的程序集。
因此我的问题是:gcc在这个函数中可以做什么样的优化?
答案 0 :(得分:2)
您的未优化函数每字节移动字节数!
如果你首先对长度进行校准,那么你可以一次移动4个字节,其余的1..3个字节会移动。如果可以确保正确的(4字节)内存对齐,则复制功能也应该更快。 并且不需要在堆栈上递增指针,您可以使用寄存器。 所有这些都会大大提高功能的速度。
或使用像memmove这样的专用mem移动功能!
答案 1 :(得分:2)
优化的代码非常混乱,但我可以发现3个循环(靠近L6,L13和L12)。我认为gcc做的是@GJ建议的(我赞成他)。 L6附近的循环每次移动4个字节,而循环#2仅移动一个字节,有时仅在循环#1之后执行。我仍然无法获得循环#3,因为它与循环#2相同。
答案 2 :(得分:1)
优化的类型取决于函数及其属性,如果函数被标记为内联,并且足够小,它将被转换为MOV
的循环和展开循环,这比{{REP
快。基于1}}的变体(它可以避免寄存器溢出)。对于未知大小,您可以获得REP MOVS
系列指令(从最大字长开始,以减少常量大小的循环数量,否则它将使用您复制的数据单元的大小)。
如果SSE被启用,它很可能会使用未展开的未对齐移动(MOVDQU
),其中长度允许或循环未对齐移动(如果它将使用时间预取,则不知道,其中的增益取决于块尺寸)如果长度足够大。如果源/ dest正确对齐,它将尝试使用更快对齐的变体。
就目前而言,如果没有内联,那么你可以获得最好的功能MOVSB
。
答案 3 :(得分:0)
gcc可以生成的最快的x86汇编指令是rep movsd
,它一次可以复制4个字节。 memcpy
中的标准libc函数<string.h>
,以及memcpy
的{{1}}特殊内联函数以及<string.h>
中的许多其他函数可为您提供最快的结果。
答案 4 :(得分:0)
您也可以在此处使用restrict。