我在 Delphi 中编写了一个简单的 for 循环。\n相同的程序在 Julia 1.6 中快了 7.6 倍。
\nprocedure TfrmTester.btnForLoopClick(Sender: TObject);\nVAR\n i, Total, Big, Small: Integer;\n s: string;\nbegin \n TimerStart;\n\n Total:= 0;\n Big := 0;\n Small:= 0;\n for i:= 1 to 1000000000 DO //1 billion\n begin\n Total:= Total+1;\n if Total > 500000\n then Big:= Big+1\n else Small:= Small+1;\n end;\n\n s:= TimerElapsedS;\n //here code to show Big/Small on the screen\nend;\nRun Code Online (Sandbox Code Playgroud)\nASM 代码对我来说似乎不错:
\nTesterForm.pas.111: TimerStart;\n007BB91D E8DE7CF9FF call TimerStart\nTesterForm.pas.113: Total:= 0;\n007BB922 33C0 xor eax,eax\n007BB924 8945F4 mov [ebp-$0c],eax\nTesterForm.pas.114: Big := 0;\n007BB927 33C0 xor eax,eax\n007BB929 8945F0 mov [ebp-$10],eax\nTesterForm.pas.115: Small:= 0;\n007BB92C 33C0 xor eax,eax\n007BB92E 8945EC mov [ebp-$14],eax\nTesterForm.pas.**116**: for i:= 1 to 1000000000 DO //1 billion\n007BB931 C745F801000000 mov [ebp-$08],$00000001\nTesterForm.pas.118: Total:= Total+1;\n007BB938 FF45F4 inc dword ptr [ebp-$0c]\nTesterForm.pas.119: if Total > 500000\n007BB93B 817DF420A10700 cmp [ebp-$0c],$0007a120\n007BB942 7E05 jle $007bb949\nTesterForm.pas.120: then Big:= Big+1\n007BB944 FF45F0 inc dword ptr [ebp-$10]\n007BB947 EB03 jmp $007bb94c\nTesterForm.pas.121: else Small:= Small+1;\n007BB949 FF45EC inc dword ptr [ebp-$14]\nTesterForm.pas.122: end;\n007BB94C FF45F8 inc dword ptr [ebp-$08]\nTesterForm.pas.**116**: for i:= 1 to 1000000000 DO //1 billion\n007BB94F 817DF801CA9A3B cmp [ebp-$08],$3b9aca01\n007BB956 75E0 jnz $007bb938\nTesterForm.pas.124: s:= TimerElapsedS;\n007BB958 8D45E8 lea eax,[ebp-$18]\nRun Code Online (Sandbox Code Playgroud)\n与 Julia 相比,Delphi 的分数怎么会如此可怜?\n我可以做些什么来改进编译器生成的代码吗?
\n信息
\n我的Delphi 10.4.2程序是Win32位的。当然,我在“发布”模式下运行:)
\n
但上面的ASM代码是针对“调试”版本的,因为我不知道当我运行优化的EXE文件时如何暂停程序的执行。但发布和调试 exe 之间的差异非常小(1.8 秒与 1.5 秒)。Julia 只需 195 毫秒即可完成。
\n更多讨论
\n我不得不提一下,当你第一次在 Julia 中运行代码时,它的时间长得离谱,因为 Julia 是 JIT,所以它必须先编译代码。编译时间(因为它是“一次性”)未包含在测量中。
\n此外,正如 AmigoJack 评论的那样,Delphi 代码几乎可以在任何地方运行,而 Julia 代码可能只能在具有现代 CPU 来支持所有这些新/奇特指令的计算机中运行。我确实有 2004 年生产的小工具,至今仍在运行。
\n无论 Julia 生成什么代码,除非安装了 Julia,否则都无法交付给“客户”。
\n无论如何,综上所述,令人遗憾的是 Delphi 编译器如此过时。
\n我进行了其他测试,发现在字符串列表中查找最短和最长的字符串在 Delphi 中比 Julia 快 10 倍。分配小内存块(10000x10000x4 字节)具有相同的速度。
\n正如 AhnLab 提到的,我进行了相当“干”的测试。我想需要编写一个执行更复杂/现实任务的完整程序,并在程序结束时查看 Julia 是否仍然优于 Delphi 7x。
\n更新
\n好吧,Julia 代码对我来说似乎完全陌生。似乎使用更现代的操作:
\n; \xe2\x94\x8c @ Julia_vs_Delphi.jl:4 within `for_fun`\n pushq %rbp\n movq %rsp, %rbp\n subq $96, %rsp\n vmovdqa %xmm11, -16(%rbp)\n vmovdqa %xmm10, -32(%rbp)\n vmovdqa %xmm9, -48(%rbp)\n vmovdqa %xmm8, -64(%rbp)\n vmovdqa %xmm7, -80(%rbp)\n vmovdqa %xmm6, -96(%rbp)\n movq %rcx, %rax\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:8 within `for_fun`\n; \xe2\x94\x82\xe2\x94\x8c @ range.jl:5 within `Colon`\n; \xe2\x94\x82\xe2\x94\x82\xe2\x94\x8c @ range.jl:354 within `UnitRange`\n; \xe2\x94\x82\xe2\x94\x82\xe2\x94\x82\xe2\x94\x8c @ range.jl:359 within `unitrange_last`\n testq %rdx, %rdx\n; \xe2\x94\x82\xe2\x94\x94\xe2\x94\x94\xe2\x94\x94\n jle L80\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl within `for_fun`\n movq %rdx, %rcx\n sarq $63, %rcx\n andnq %rdx, %rcx, %r9\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:13 within `for_fun`\n cmpq $8, %r9\n jae L93\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl within `for_fun`\n movl $1, %r10d\n xorl %edx, %edx\n xorl %r11d, %r11d\n jmp L346\nL80:\n xorl %edx, %edx\n xorl %r11d, %r11d\n xorl %r9d, %r9d\n jmp L386\nL93: movabsq $9223372036854775800, %r8 # imm = 0x7FFFFFFFFFFFFFF8\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:13 within `for_fun`\n andq %r9, %r8\n leaq 1(%r8), %r10\n movabsq $.rodata.cst32, %rcx\n vmovdqa (%rcx), %ymm1\n vpxor %xmm0, %xmm0, %xmm0\n movabsq $.rodata.cst8, %rcx\n vpbroadcastq (%rcx), %ymm2\n movabsq $1023787240, %rcx # imm = 0x3D05C0E8\n vpbroadcastq (%rcx), %ymm3\n movabsq $1023787248, %rcx # imm = 0x3D05C0F0\n vpbroadcastq (%rcx), %ymm5\n vpcmpeqd %ymm6, %ymm6, %ymm6\n movabsq $1023787256, %rcx # imm = 0x3D05C0F8\n vpbroadcastq (%rcx), %ymm7\n movq %r8, %rcx\n vpxor %xmm4, %xmm4, %xmm4\n vpxor %xmm8, %xmm8, %xmm8\n vpxor %xmm9, %xmm9, %xmm9\n nopw %cs:(%rax,%rax)\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl within `for_fun`\nL224:\n vpaddq %ymm2, %ymm1, %ymm10\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:10 within `for_fun`\n vpxor %ymm3, %ymm1, %ymm11\n vpcmpgtq %ymm11, %ymm5, %ymm11\n vpxor %ymm3, %ymm10, %ymm10\n vpcmpgtq %ymm10, %ymm5, %ymm10\n vpsubq %ymm11, %ymm0, %ymm0\n vpsubq %ymm10, %ymm4, %ymm4\n vpaddq %ymm11, %ymm8, %ymm8\n vpsubq %ymm6, %ymm8, %ymm8\n vpaddq %ymm10, %ymm9, %ymm9\n vpsubq %ymm6, %ymm9, %ymm9\n vpaddq %ymm7, %ymm1, %ymm1\n addq $-8, %rcx\n jne L224\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:13 within `for_fun`\n vpaddq %ymm8, %ymm9, %ymm1\n vextracti128 $1, %ymm1, %xmm2\n vpaddq %xmm2, %xmm1, %xmm1\n vpshufd $238, %xmm1, %xmm2 # xmm2 = xmm1[2,3,2,3]\n vpaddq %xmm2, %xmm1, %xmm1\n vmovq %xmm1, %r11\n vpaddq %ymm0, %ymm4, %ymm0\n vextracti128 $1, %ymm0, %xmm1\n vpaddq %xmm1, %xmm0, %xmm0\n vpshufd $238, %xmm0, %xmm1 # xmm1 = xmm0[2,3,2,3]\n vpaddq %xmm1, %xmm0, %xmm0\n vmovq %xmm0, %rdx\n cmpq %r8, %r9\n je L386\nL346:\n leaq 1(%r9), %r8\n nop\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:10 within `for_fun`\n; \xe2\x94\x82\xe2\x94\x8c @ operators.jl:378 within `>`\n; \xe2\x94\x82\xe2\x94\x82\xe2\x94\x8c @ int.jl:83 within `<`\nL352:\n xorl %ecx, %ecx\n cmpq $500000, %r10 # imm = 0x7A120\n seta %cl\n cmpq $500001, %r10 # imm = 0x7A121\n; \xe2\x94\x82\xe2\x94\x94\xe2\x94\x94\n adcq $0, %rdx\n addq %rcx, %r11\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:13 within `for_fun`\n; \xe2\x94\x82\xe2\x94\x8c @ range.jl:837 within `iterate`\n incq %r10\n; \xe2\x94\x82\xe2\x94\x82\xe2\x94\x8c @ promotion.jl:468 within `==`\n cmpq %r10, %r8\n; \xe2\x94\x82\xe2\x94\x94\xe2\x94\x94\n jne L352\n; \xe2\x94\x82 @ Julia_vs_Delphi.jl:17 within `for_fun`\nL386:\n movq %r9, (%rax)\n movq %rdx, 8(%rax)\n movq %r11, 16(%rax)\n vmovaps -96(%rbp), %xmm6\n vmovaps -80(%rbp), %xmm7\n vmovaps -64(%rbp), %xmm8\n vmovaps -48(%rbp), %xmm9\n vmovaps -32(%rbp), %xmm10\n vmovaps -16(%rbp), %xmm11\n addq $96, %rsp\n popq %rbp\n vzeroupper\n retq\n nopw %cs:(%rax,%rax)\nRun Code Online (Sandbox Code Playgroud)\n
首先让我们注意到优化编译器没有理由实际执行循环,目前 Delphi 和 Julia 输出类似的汇编器,实际运行循环,但编译器将来可以跳过循环并分配值。微基准测试很棘手。
不同之处似乎在于 Julia 使用SIMD 指令,这对于此类循环非常有意义(~8 倍加速非常有意义,具体取决于您的 CPU)。
您可以查看这篇博客文章,了解有关 Delphi 中 SIMD 的想法。
尽管这不是答案的要点,但我将稍微扩展一下完全删除循环的可能性。我不确定 Delphi 规范怎么说,但在许多编译语言中,包括 Julia(“提前”),编译器可以简单地找出循环后变量的状态并替换循环与那个状态。看一下下面的 C++ 代码(编译器资源管理器):
#include <cstdio>
void loop() {
long total = 0, big = 0, small = 0;
for (long i = 0; i < 100; ++i) {
total++;
if (total > 50) {
big++;
} else {
small++;
}
}
std::printf("%ld %ld %ld", total, big, small);
}
Run Code Online (Sandbox Code Playgroud)
这是汇编程序 clang trunk 输出:
loop(): # @loop()
lea rdi, [rip + .L.str]
mov esi, 100
mov edx, 50
mov ecx, 50
xor eax, eax
jmp printf@PLT # TAILCALL
.L.str:
.asciz "%ld %ld %ld"
Run Code Online (Sandbox Code Playgroud)
如您所见,没有循环,只有结果。对于较长的循环,clang 会停止进行此优化,但这只是编译器的限制,其他编译器可以以不同的方式执行此操作,并且我确信有一个高度优化的编译器可以处理更复杂的情况。
| 归档时间: |
|
| 查看次数: |
373 次 |
| 最近记录: |