我在Delphi中写了一个简单的for循环,同样的程序在Julia 1.6中要快7.6倍。
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;
//here code to show Big/Small on the screen
end;
ASM代码在我看来很不错:
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]
Delphi怎么会有这么可怜的分数和Julia相比呢?我能做些什么来改进编译器生成的代码吗?
- 信息**
我的Delphi 10.4.2程序是Win32位的,当然我是在"Release"模式下运行的:)
但是上面的ASM代码是针对"调试"版本的,因为我不知道在运行优化的EXE文件时如何暂停程序的执行。但是Release和Debug exe之间的差异非常小(1.8 vs 1.5秒)。Julia在195ms内完成了这一操作。
- 更多讨论**
1.我不得不提的是,当你第一次在Julia中运行代码时,它的时间高得离谱,因为Julia是JIT,所以它必须先编译代码,编译时间(因为它是"一次性"的)没有包括在测量中。
1.此外,正如AmigoJack评论的那样,Delphi代码几乎可以在任何地方运行,而Julia代码可能只能在拥有现代CPU以支持所有这些新的/花哨的指令的计算机上运行。我确实有一些小工具,我早在2004年就生产了,现在仍然在运行。
- Julia生成的任何代码都不能交付给"客户",除非Julia安装了这些代码。
不管怎么说,所有这些都说了,令人伤心的是,Delphi编译器是如此过时。
1.我做了其他测试,发现在Delphi中查找字符串列表中最短和最长的字符串比Julia快10倍,分配小块内存(10000x10000x4字节)也有同样的速度。
1.正如AhnLab提到的,我运行相当"干"的测试。我猜需要编写一个执行更复杂/现实任务的完整程序,并在程序结束时看看Julia是否仍然优于Delphi 7X。
- 更新**
好吧,朱莉娅密码对我来说完全陌生。似乎使用了更现代的操作:
; ┌ @ 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)
1条答案
按热度按时间ecbunoof1#
让我们先来注意一下,优化编译器没有理由实际执行循环,目前 Delphi 和Julia输出类似的汇编程序,实际运行整个循环,但将来编译器可以跳过循环并赋值。
不同之处似乎是Julia使用了SIMD instructions,这对此类循环非常有意义(根据CPU的不同,大约8倍的加速非常有意义)。
您可以查看this blog post,了解有关 Delphi 中SIMD的想法。
虽然这不是答案的要点,但我将进一步说明完全删除循环的可能性。我不知道 Delphi 规范是怎么说的,但在许多编译语言中,包括Julia(“刚好提前”),编译器可以简单地计算出循环后变量的状态,并用该状态替换循环。2看看下面的C++代码(compiler explorer):
这是汇编程序的clang trunk输出:
如你所见,没有循环,只有结果,对于更长的循环,clang停止优化,但这只是编译器的一个限制,其他编译器可以做不同的,我相信有一个高度优化的编译器,可以处理更复杂的情况。