You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Clang、GCC等编译器生成低效的循环展开代码?

为何编译器会生成低效的循环展开代码?

编译环境

  • GCC 版本 14.2
  • Clang 版本 19.1.0
  • 编译参数:-Ofast -lm

示例代码(变体1)

#define MIN(a,b) ((a) < (b)? (a) : (b))
void to_test(size_t num_iters)
{
    const size_t exec_iters = MIN(num_iters, 10);

    #if __GNUC__
    #pragma GCC unroll 10
    #elif __clang__
    #pragma unroll 10
    #endif
    for (size_t i = 0; i < exec_iters; ++i) {
        printf("%d\n", i);
    }
}

两款编译器都会展开循环,但每次执行后都插入条件跳转判断是否结束,生成的汇编代码效率极低,大致如下:

to_test:
        test    rdi, rdi
        je      .L33
        push    rbx
        xor     esi, esi
        mov     rbx, rdi
        xor     eax, eax
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 1
        je      .L1
        xor     eax, eax
        mov     esi, 1
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 2
        je      .L1
        xor     eax, eax
        mov     esi, 2
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 3
        je      .L1
        xor     eax, eax
        mov     esi, 3
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 4
        je      .L1
        xor     eax, eax
        mov     esi, 4
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 5
        je      .L1
        xor     eax, eax
        mov     esi, 5
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 6
        je      .L1
        xor     eax, eax
        mov     esi, 6
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 7
        je      .L1
        xor     eax, eax
        mov     esi, 7
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 8
        je      .L1
        xor     eax, eax
        mov     esi, 8
        mov     edi, OFFSET FLAT:.LC0
        call    printf
        cmp     rbx, 9
        je      .L1
        mov     esi, 9
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        pop     rbx
        jmp     printf

示例代码(变体2)

void to_test(size_t num_iters)
{
    const size_t exec_iters = fmin(num_iters, 10);

    #if __GNUC__
    #pragma GCC unroll 10
    #elif __clang__
    #pragma unroll 10
    #endif
    for (size_t i = 0; i < exec_iters; ++i) {
        printf("%d\n", i);
    }
}

两款编译器的表现出现差异:

  • GCC 生成包含大量比较的线性搜索代码
  • Clang 会先判断迭代次数是否为10,若是则生成完全展开的无检查循环;否则不展开循环,仅生成普通循环

这些实现的效率都不理想,尤其是GCC的线性搜索和Clang在迭代次数小于10时不展开的行为。

期望的优化方向

理想情况下,编译器可以生成更高效的代码,比如通过计算起始索引直接跳转到对应位置执行:

// 假设 exec_iters = 4
// 则 start_idx = 4 - 10 = -6
const start_idx = -6;
jump label_fabs({start_idx})

:label_0
printf("%d\n", start_idx + 0);     // i = 0
:label_1
printf("%d\n", start_idx + 1);     // i = 1
:label_2
printf("%d\n", start_idx + 2);     // i = 2
:label_3
printf("%d\n", start_idx + 3);     // i = 3
:label_4
printf("%d\n", start_idx + 4);     // i = 4
:label_5
printf("%d\n", start_idx + 5);     // i = 5
:label_6
printf("%d\n", start_idx + 6);     // i = 6
:label_7
printf("%d\n", start_idx + 7);     // i = 7
:label_8
printf("%d\n", start_idx + 8);     // i = 8
:label_9
printf("%d\n", start_idx + 9);     // i = 9

或者生成对应不同迭代次数的10个函数,直接跳转执行。

尝试调整代码为固定循环结束点、变量起始点的写法,仍未得到更优结果:

#define MIN(a,b) ((a) < (b)? (a) : (b))
void to_test(size_t num_iters)
{
    const size_t exec_iters = MIN(num_iters, 10);

    int loop_start = 10 - exec_iters;
    int base_offset = exec_iters - 10;
    int running_offset = loop_start;

    #if __GNUC__
    #pragma GCC unroll 10
    #elif __clang__
    #pragma unroll 10
    #endif
    for (size_t i = loop_start; i < 10; ++i) {
        printf("%d\n", base_offset + running_offset);
        ++running_offset;
    }
}

内容的提问来源于stack exchange,提问作者user4833973

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:54:55