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

Delphi循环性能远逊Julia:编译器生成ASM代码是否过时?

Delphi循环性能为何比Julia慢7.6倍?优化方向探讨

测试场景与原始代码

我在Delphi 10.4.2中编写了一个10亿次的简单循环,Win32 Release模式下耗时1.5秒(Debug版1.8秒,差异极小),而相同逻辑在Julia 1.6中仅需195毫秒(不计JIT首次编译的一次性开销)。

Delphi原始代码

procedure TfrmTester.btnForLoopClick(Sender: TObject);
VAR
   i, Total, Big, Small: Integer;
   s: string;
begin   
  TimerStart;

   Total:= 0;
   Big  := 0;
   Small:= 0;
   for i:= 1 to 1000000000 DO    //1 billion
    begin
      Total:= Total+1;
      if Total > 500000
      then Big:= Big+1
      else Small:= Small+1;
    end;

 s:= TimerElapsedS;
 // 显示Big/Small的代码
end;

Delphi Debug版生成的汇编代码

TesterForm.pas.111: TimerStart;
007BB91D E8DE7CF9FF       call TimerStart
TesterForm.pas.113: Total:= 0;
007BB922 33C0             xor eax,eax
007BB924 8945F4           mov [ebp-$0c],eax
TesterForm.pas.114: Big  := 0;
007BB927 33C0             xor eax,eax
007BB929 8945F0           mov [ebp-$10],eax
TesterForm.pas.115: Small:= 0;
007BB92C 33C0             xor eax,eax
007BB92E 8945EC           mov [ebp-$14],eax
TesterForm.pas.116: for i:= 1 to 1000000000 DO    //1 billion
007BB931 C745F801000000   mov [ebp-$08],$00000001
TesterForm.pas.118: Total:= Total+1;
007BB938 FF45F4           inc dword ptr [ebp-$0c]
TesterForm.pas.119: if Total > 500000
007BB93B 817DF420A10700   cmp [ebp-$0c],$0007a120
007BB942 7E05             jle $007bb949
TesterForm.pas.120: then Big:= Big+1
007BB944 FF45F0           inc dword ptr [ebp-$10]
007BB947 EB03             jmp $007bb94c
TesterForm.pas.121: else Small:= Small+1;
007BB949 FF45EC           inc dword ptr [ebp-$14]
TesterForm.pas.122: end;
007BB94C FF45F8           inc dword ptr [ebp-$08]
TesterForm.pas.116: for i:= 1 to 1000000000 DO    //1 billion
007BB94F 817DF801CA9A3B   cmp [ebp-$08],$3b9aca01
007BB956 75E0             jnz $007bb938
TesterForm.pas.124: s:= TimerElapsedS;
007BB958 8D45E8           lea eax,[ebp-$18]

性能差异的核心原因

对比Julia生成的汇编代码(见下文),关键差异如下:

  1. 指令集利用:Julia生成了基于AVX2的64位向量代码(使用ymm寄存器、vpaddq/vpcmpgtq等向量指令),一次可批量处理8个64位整数;而Delphi的Win32代码仅使用32位通用寄存器,每次只能处理一个整数。
  2. 变量存储:Delphi将Total/Big/Small存储在栈内存中([ebp-$xx]),每次操作都要读写内存;Julia则将变量放入寄存器甚至向量寄存器,避免了内存访问开销。
  3. 编译器优化策略:Julia的JIT编译器会针对当前CPU自动做循环向量化、展开等优化;而Delphi的Win32编译器对这类简单循环的自动向量化支持不足,未做批量处理优化。

Julia生成的汇编代码(64位AVX2优化)

; ┌ @ Julia_vs_Delphi.jl:4 within `for_fun`
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $96, %rsp
        vmovdqa %xmm11, -16(%rbp)
        vmovdqa %xmm10, -32(%rbp)
        vmovdqa %xmm9, -48(%rbp)
        vmovdqa %xmm8, -64(%rbp)
        vmovdqa %xmm7, -80(%rbp)
        vmovdqa %xmm6, -96(%rbp)
        movq    %rcx, %rax
; │ @ Julia_vs_Delphi.jl:8 within `for_fun`
; │┌ @ range.jl:5 within `Colon`
; ││┌ @ range.jl:354 within `UnitRange`
; │││┌ @ range.jl:359 within `unitrange_last`
        testq   %rdx, %rdx
; │└└└
        jle     L80
; │ @ Julia_vs_Delphi.jl within `for_fun`
        movq    %rdx, %rcx
        sarq    $63, %rcx
        andnq   %rdx, %rcx, %r9
; │ @ Julia_vs_Delphi.jl:13 within `for_fun`
        cmpq    $8, %r9
        jae     L93
; │ @ Julia_vs_Delphi.jl within `for_fun`
        movl    $1, %r10d
        xorl    %edx, %edx
        xorl    %r11d, %r11d
        jmp     L346
L80:
        xorl    %edx, %edx
        xorl    %r11d, %r11d
        xorl    %r9d, %r9d
        jmp     L386
L93:    movabsq $9223372036854775800, %r8       # imm = 0x7FFFFFFFFFFFFFF8
; │ @ Julia_vs_Delphi.jl:13 within `for_fun`
        andq    %r9, %r8
        leaq    1(%r8), %r10
        movabsq $.rodata.cst32, %rcx
        vmovdqa (%rcx), %ymm1
        vpxor   %xmm0, %xmm0, %xmm0
        movabsq $.rodata.cst8, %rcx
        vpbroadcastq    (%rcx), %ymm2
        movabsq $1023787240, %rcx               # imm = 0x3D05C0E8
        vpbroadcastq    (%rcx), %ymm3
        movabsq $1023787248, %rcx               # imm = 0x3D05C0F0
        vpbroadcastq    (%rcx), %ymm5
        vpcmpeqd        %ymm6, %ymm6, %ymm6
        movabsq $1023787256, %rcx               # imm = 0x3D05C0F8
        vpbroadcastq    (%rcx), %ymm7
        movq    %r8, %rcx
        vpxor   %xmm4, %xmm4, %xmm4
        vpxor   %xmm8, %xmm8, %xmm8
        vpxor   %xmm9, %xmm9, %xmm9
        nopw    %cs:(%rax,%rax)
; │ @ Julia_vs_Delphi.jl within `for_fun`
L224:
        vpaddq  %ymm2, %ymm1, %ymm10
; │ @ Julia_vs_Delphi.jl:10 within `for_fun`
        vpxor   %ymm3, %ymm1, %ymm11
        vpcmpgtq        %ymm11, %ymm5, %ymm11
        vpxor   %ymm3, %ymm10, %ymm10
        vpcmpgtq        %ymm10, %ymm5, %ymm10
        vpsubq  %ymm11, %ymm0, %ymm0
        vpsubq  %ymm10, %ymm4, %ymm4
        vpaddq  %ymm11, %ymm8, %ymm8
        vpsubq  %ymm6, %ymm8, %ymm8
        vpaddq  %ymm10, %ymm9, %ymm9
        vpsubq  %ymm6, %ymm9, %ymm9
        vpaddq  %ymm7, %ymm1, %ymm1
        addq    $-8, %rcx
        jne     L224
; │ @ Julia_vs_Delphi.jl:13 within `for_fun`
        vpaddq  %ymm8, %ymm9, %ymm1
        vextracti128    $1, %ymm1, %xmm2
        vpaddq  %xmm2, %xmm1, %xmm1
        vpshufd $238, %xmm1, %xmm2              # xmm2 = xmm1[2,3,2,3]
        vpaddq  %xmm2, %xmm1, %xmm1
        vmovq   %xmm1, %r11
        vpaddq  %ymm0, %ymm4, %ymm0
        vextracti128    $1, %ymm0, %xmm1
        vpaddq  %xmm1, %xmm0, %xmm0
        vpshufd $238, %xmm0, %xmm1              # xmm1 = xmm0[2,3,2,3]
        vpaddq  %xmm1, %xmm0, %xmm0
        vmovq   %xmm0, %rdx
        cmpq    %r8, %r9
        je      L386
L346:
        leaq    1(%r9), %r8
        nop
; │ @ Julia_vs_Delphi.jl:10 within `for_fun`
; │┌ @ operators.jl:378 within `>`
; ││┌ @ int.jl:83 within `<`
L352:
        xorl    %ecx, %ecx
        cmpq    $500000, %r10                   # imm = 0x7A120
        seta    %cl
        cmpq    $500001, %r10                   # imm = 0x7A121
; │└└
        adcq    $0, %rdx
        addq    %rcx, %r11
; │ @ Julia_vs_Delphi.jl:13 within `for_fun`
; │┌ @ range.jl:837 within `iterate`
        incq    %r10
; ││┌ @ promotion.jl:468 within `==`
        cmpq    %r10, %r8
; │└└
        jne     L352
; │ @ Julia_vs_Delphi.jl:17 within `for_fun`
L386:
        movq    %r9, (%rax)
        movq    %rdx, 8(%rax)
        movq    %r11, 16(%rax)
        vmovaps -96(%rbp), %xmm6
        vmovaps -80(%rbp), %xmm7
        vmovaps -64(%rbp), %xmm8
        vmovaps -48(%rbp), %xmm9
        vmovaps -32(%rbp), %xmm10
        vmovaps -16(%rbp), %xmm11
        addq    $96, %rsp
        popq    %rbp
        vzeroupper
        retq
        nopw    %cs:(%rax,%rax)

Delphi可尝试的优化方案

1. 改用Win64目标平台

切换到64位编译后,编译器可使用更多通用寄存器,支持AVX等现代指令集,自动优化能力会显著提升,部分场景下可接近Julia的性能。

2. 手动消除循环内分支

本次测试的分支条件在循环执行500000次后就不再变化,可直接拆分循环,完全消除分支判断:

procedure TfrmTester.btnForLoopClick(Sender: TObject);
VAR
   i, Total, Big, Small: Integer;
   s: string;
begin   
  TimerStart;

   Total := 0;
   Big := 0;
   Small := 0;
   // 前500000次循环,Small递增
   for i := 1 to 500000 do
   begin
     Inc(Small);
     Inc(Total);
   end;
   // 剩余999500000次循环,直接赋值Big,无需循环
   Big := 999500000;
   Total := 1000000000;

 s:= TimerElapsedS;
 // 显示Big/Small的代码
end;

该优化会让编译器直接把循环替换为常数赋值,耗时几乎为0。

3. 开启编译器最高优化

在项目选项中开启以下设置:

  • 勾选Full Optimization
  • 关闭Debug Information
  • 开启Inline Function Expansion
  • 勾选Remove Redundant Code

4. 手动使用寄存器或内嵌汇编

将变量声明为register类型,让编译器优先放入寄存器;或直接编写内嵌汇编,利用SIMD指令批量处理计数。

补充讨论

  • Julia的优势集中在数值计算场景,JIT编译可针对当前CPU生成最优向量代码,但需要依赖Julia运行时,且首次启动有编译开销。
  • Delphi的优势在于兼容性(可在老系统运行)、无需额外运行时,在字符串操作、内存管理等业务场景中可能表现更优(测试中字符串列表查找Delphi比Julia快10倍)。
  • 本次测试为极端简单的循环场景,真实业务场景中性能差异可能大幅缩小。

内容的提问来源于stack exchange,提问作者Error - CPU Not Foud

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:25:25