复制数组时的 gcc 优化

问题描述 投票:0回答:5

我需要分析一个执行大量数组副本的应用程序,所以我最终分析了这个非常简单的函数:

typedef unsigned char UChar;
void copy_mem(UChar *src, UChar *dst, unsigned int len) {
        UChar *end = src + len;
        while (src < end)
                *dst++ = *src++;
}

我使用 Intel 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 加载一个双字并将低字节放入 edx,然后将其存储到 *dst 并更新指针:足够简单。

然后看到优化版,啥也不懂。

编辑这里有优化的组件。

因此我的问题是:gcc 可以在这个函数中进行什么样的优化?

c assembly optimization
5个回答
2
投票

优化后的代码相当混乱,但我可以发现 3 个循环(靠近 L6、L13 和 L12)。我认为 gcc 做了@GJ 的建议(我给他投了票)。 L6 附近的循环每次移动 4 个字节,而循环 #2 只移动 1 个字节,并且有时只在循环 #1 之后执行。我仍然无法获得循环#3,因为它与循环#2 相同。


2
投票

未优化的函数逐字节移动字节!

如果您先计算长度,那么您可以一次移动 4 个字节,其余 1..3 个字节手动移动。如果您可以确保正确的(4 字节)内存对齐,则复制功能也应该更快。 并且不需要递增堆栈上的指针,可以使用寄存器。 所有这些都将极大地提高功能速度。

或者使用专用的 mem move 函数,如 memmove!


1
投票

优化的类型取决于函数及其属性,如果函数被标记为内联,并且足够小,它将变成

MOV
的展开循环,这比基于
REP
的变体更快(并且可以避免寄存器溢出)。对于未知大小,您将获得
REP MOVS
系列指令(从最大字大小开始,以减少恒定大小的循环量,否则它将使用您复制的数据单元的大小)。

如果启用了 SSE,它很可能会在长度允许的情况下使用展开的未对齐移动 (

MOVDQU
) 或循环未对齐移动(不知道是否会使用时间预取,从中获得的收益取决于块大小),如果长度足够大。如果源/目标正确对齐,它将尝试使用更快对齐的变体。

就目前而言,当它未内联时,您可以使用该功能的最佳方式是

MOVSB


0
投票

gcc 可以生成的最快的 x86 汇编指令是

rep movsd
,它一次复制 4 个字节。
memcpy
中的标准 libc 函数
<string.h>
memcpy
中的特殊内联 gcc 以及
<string.h>
中的许多其他函数一起为您提供最快的结果。


0
投票

您还可以从此处使用 restrict 中受益。

© www.soinside.com 2019 - 2024. All rights reserved.