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

循环分块优化:GCC与Clang性能差异探究

循环分块优化在GCC与Clang下的性能差异原因分析

我尝试使用循环分块(Loop Tiling)方法进行L1缓存优化,实现了原循环与分块循环的代码,并通过基准测试对比两者性能。测试发现:在GCC 12.2 -O3编译下,分块循环性能优于原循环;但在Clang 15.0.0 -O3编译下,分块循环性能与原循环相当甚至更差,添加-march=native参数后,分块循环性能差10倍。以下是测试代码、性能结果及汇编代码,现将差异原因分析如下:


测试代码

#include <chrono>
#include <iostream>

const int N = 10000;
const int blockSize = 16;
int array[N];
int sum;

// 原循环
void original_loop() {
  for (int i = 0; i < N; i++) {
    sum += array[i];
  }
}

// 分块循环
void tiled_loop() {
  for (int i = 0; i < N; i += blockSize) {
    for (int j = 0; j < blockSize; j++) {
      sum += array[i + j];
    }
  }
}

int main() {
  // 初始化数组
  for (int i = 0; i < N; i++) {
    array[i] = i;
  }

  // 基准测试原循环
  auto start = std::chrono::high_resolution_clock::now();
  for (int i = 0; i < 100000; i++) {
    sum = 0;
    original_loop();
  }
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Original loop: " << elapsed.count() << " seconds" << std::endl;

  // 基准测试分块循环
  start = std::chrono::high_resolution_clock::now();
  for (int i = 0; i < 100000; i++) {
    sum = 0;
    tiled_loop();
  }
  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Tiled loop: " << elapsed.count() << " seconds" << std::endl;

  return 0;
}

性能测试结果

当N=1000000、blockSize=16时

**GCC**
Original loop: 11.1892 seconds
Tiled loop: 9.67448 seconds
**Clang**  
Original loop: 8.52184 seconds
Tiled loop: 8.67858 seconds

当N=10000、blockSize=16时

**GCC**
Original loop: 0.094786 seconds
Tiled loop: 0.0436597 seconds
**Clang**
Original loop: 0.0416874 seconds
Tiled loop: 0.0610718 seconds

添加-march=native参数后Clang测试结果

Original loop: 0.0292406 seconds
Tiled loop: 0.173324 seconds

生成的汇编代码

GCC生成的汇编

original_loop():
        mov     ecx, DWORD PTR sum[rip]
        mov     eax, OFFSET FLAT:array
        mov     edx, OFFSET FLAT:array+40000
        pxor    xmm0, xmm0
.L2:
        paddd   xmm0, XMMWORD PTR [rax]
        add     rax, 16
        cmp     rdx, rax
        jne     .L2
        movdqa  xmm1, xmm0
        psrldq  xmm1, 8
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        psrldq  xmm1, 4
        paddd   xmm0, xmm1
        movd    eax, xmm0
        add     eax, ecx
        mov     DWORD PTR sum[rip], eax
        ret
tiled_loop():
        pxor    xmm1, xmm1
        mov     eax, OFFSET FLAT:array
        mov     edx, OFFSET FLAT:array+40000
        movd    xmm3, DWORD PTR sum[rip]
        movdqa  xmm2, xmm1
        movdqa  xmm0, xmm1
.L6:
        paddd   xmm3, XMMWORD PTR [rax]
        paddd   xmm0, XMMWORD PTR [rax+16]
        add     rax, 64
        paddd   xmm2, XMMWORD PTR [rax-32]
        paddd   xmm1, XMMWORD PTR [rax-16]
        cmp     rdx, rax
        jne     .L6
        paddd   xmm0, xmm3
        paddd   xmm0, xmm2
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        psrldq  xmm1, 8
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        psrldq  xmm1, 4
        paddd   xmm0, xmm1
        movd    DWORD PTR sum[rip], xmm0
        ret
sum:
        .zero   4
array:
        .zero   40000

Clang生成的汇编

original_loop():                     # @original_loop()
        pxor    xmm0, xmm0
        mov     eax, 12
        movd    xmm1, dword ptr [rip + sum]     # xmm1 = mem[0],zero,zero,zero
        lea     rcx, [rip + array]
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        paddd   xmm1, xmmword ptr [rcx + 4*rax - 48]
        paddd   xmm0, xmmword ptr [rcx + 4*rax - 32]
        paddd   xmm1, xmmword ptr [rcx + 4*rax - 16]
        paddd   xmm0, xmmword ptr [rcx + 4*rax]
        add     rax, 16
        cmp     rax, 10012
        jne     .LBB0_1
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238                 # xmm1 = xmm0[2,3,2,3]
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85                  # xmm0 = xmm1[1,1,1,1]
        paddd   xmm0, xmm1
        movd    dword ptr [rip + sum], xmm0
        ret
tiled_loop():                        # @tiled_loop()
        mov     edx, dword ptr [rip + sum]
        xor     eax, eax
        lea     rcx, [rip + array]
.LBB1_1:                                # =>This Inner Loop Header: Depth=1
        movdqa  xmm0, xmmword ptr [rcx + 4*rax]
        movdqa  xmm1, xmmword ptr [rcx + 4*rax + 16]
        paddd   xmm1, xmmword ptr [rcx + 4*rax + 48]
        paddd   xmm0, xmmword ptr [rcx + 4*rax + 32]
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238                 # xmm1 = xmm0[2,3,2,3]
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85                  # xmm0 = xmm1[1,1,1,1]
        paddd   xmm0, xmm1
        movd    esi, xmm0
        add     esi, edx
        cmp     rax, 9983
        ja      .LBB1_3
        movdqa  xmm0, xmmword ptr [rcx + 4*rax + 64]
        movdqa  xmm1, xmmword ptr [rcx + 4*rax + 80]
        paddd   xmm1, xmmword ptr [rcx + 4*rax + 112]
        paddd   xmm0, xmmword ptr [rcx + 4*rax + 96]
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238                 # xmm1 = xmm0[2,3,2,3]
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85                  # xmm0 = xmm1[1,1,1,1]
        paddd   xmm0, xmm1
        movd    edx, xmm0
        add     edx, esi
        add     rax, 32
        jmp     .LBB1_1
.LBB1_3:
        mov     dword ptr [rip + sum], esi
        ret
array:
        .zero   40000

sum:
        .long   0 

差异原因分析

  1. 向量化优化策略不同

    • GCC对分块循环做了更高效的SIMD并行优化:在tiled_loop的汇编中,一次循环迭代处理4个XMMWORD(共64字节,对应16个int),用多个XMM寄存器并行累加,最后统一合并结果,充分利用了SIMD单元的带宽,比原循环的单XMM寄存器累加效率更高。
    • Clang的原循环已经做到极致向量化:Clang对original_loop的优化直接用两个XMM寄存器交替处理4个XMMWORD,循环迭代次数极少;但分块循环的代码结构被Clang拆分成多次小SIMD累加,还额外增加了pshufd、paddd等冗余的结果合并操作,每次块处理都要把SIMD结果转成整数加到sum相关变量,额外开销抵消了分块的潜在收益。
  2. -march=native放大了差异

    • 指定-march=native后,Clang会启用CPU原生高级指令集(如AVX2),原循环会进一步优化为更宽的向量操作(如YMM寄存器),带宽利用率大幅提升;但分块循环的嵌套结构限制了Clang的优化空间,依然保留了冗余的块内合并逻辑,甚至因指令集升级后小循环的分支预测、流水线停顿问题更明显,导致性能差距被放大10倍。
  3. 缓存优化的实际收益有限

    • 测试用的数组大小为10000个int(共40KB),刚好处于多数CPU的L1数据缓存范围内(32KB-64KB),原循环本身已经是缓存友好的,分块循环的缓存优化收益本就有限。GCC能通过分块进一步提升SIMD并行度,但Clang的分块循环反而引入了额外的循环嵌套和结果合并开销,最终导致性能下降。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 06:10:30