如何在Delphi中使用现代CPU指令?(Java比Delphi快?)

WeG*_*ars 5 delphi delphi-2010 delphi-xe2

一位朋友给我发了一个最近版本的Delphi和Java之间的比较(如果你需要,可以找到源代码).信不信由你(更好地相信)Java现在比Delphi快得多,因为Delphi编译器不会利用现代CPU指令!"慢"Java的重大突破.

我的问题是:如何在不使用ASM的情况下在Delphi中使用现代CPU指令?

FastCode项目是一个部分回答上述问题,但它现在放弃了.还有其他类似FastCode的项目吗?

这是另一篇文章,显示Java和C#确实比Delphi快得多:http: //webandlife.blogspot.com/2011/12/c-performance-vs-delphi-performance.html


JAVA

import java.util.Date;

public class j
{
  public static void xxx(int n, int m)
  {
        double t;
        int i, j;
        double d, r;
        t = 0.0;
        for (j = 1; j <= n; j++)
        {
          t = t / 1000.0;
          for (i = 1; i <= m; i++)
          {
                t = t + i / 999999.0;
                d = t * t + i;
                r = (t + d) / (200000.0 * (i + 1));
                t = t - r;
          }
        }
        System.out.println(t);
  }

  public static void main(String [] args)
  {
        Date t1, t2;

        t1 = new Date();
        xxx(1, 999999999);
        t2 = new Date();
        System.out.println((t2.getTime() - t1.getTime())/1000);
        t1 = new Date();
        xxx(1, 999999999);
        t2 = new Date();
        System.out.println((t2.getTime() - t1.getTime())/1000);
  }
}
Run Code Online (Sandbox Code Playgroud)

25秒

DELPHI

program d;
{$APPTYPE CONSOLE}
uses
  System.SysUtils, System.DateUtils;
var
  t1, t2: TDateTime;

procedure xxx (n: integer; m: integer);
var
  t: double;
  i, j: integer;
  d, r: double;
begin
  t:= 0.0;
  for j:= 1 to n do
  begin
        t:= t / 1000.0;
        for i:= 1 to m do
        begin
          t:= t + i / 999999.0;
          d:= t * t + i;
          r:= (t + d) / (200000.0 * (i + 1));
          t:= t - r;
        end;
  end;
  writeln(t);
end;

begin
  t1:= Now;
  xxx(1, 999999999);
  t2:= Now;
  writeln(SecondsBetween(t2,t1));

  t1:= Now;
  xxx(1, 999999999);
  t2:= Now;
  writeln(SecondsBetween(t2,t1));
end.
Run Code Online (Sandbox Code Playgroud)

37秒


德尔福似乎仍处于链条的最底层:http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

我想知道Lazarus从这个角度来看与Delphi的比较.

Arn*_*hez 12

根据你的代码,32位Delphi编译器的缓慢是浮点运算支持,它远未优化,并且在FPU堆栈上复制了大量内容.

关于浮点运算,不仅Java JITted代码会更快.即使是现代的JavaScript JIT编译器也可以比Delphi好得多!

这篇博客文章只是对此的一个参考,并提供关于浮点的Delphi缓慢的asm级解释:

在此输入图像描述

但是如果你使用面向Win64平台的Delphi编译器,它将发出不是x87而是SSE2操作码,并且会更快.我怀疑与Java JITted可执行文件相当.

并且,就Java而言,任何Delphi可执行文件都将使用比JVM 少得多的内存,所以在这里,Delphi可执行文件完全在轨道上!

如果您希望代码更快,请不要使用asm或低级优化技巧,而是更改算法.它可能比编译提示快一个数量级.使用内联的asm操作码可以实现专用的过程 - 看看这些低级别的黑客文章.但掌握并不容易,通常,正确的软件分析和添加一些缓存是性能的最佳方式!


J..*_*... 10

从Arnaud的角度来看 - 我实际上是在delphi中为x86和x64编译的.

32位编译器:

Unit1.pas.36: t:= t / 1000.0;
0051274D DD45F0           fld qword ptr [ebp-$10]
00512750 D835E4275100     fdiv dword ptr [$005127e4]
00512756 DD5DF0           fstp qword ptr [ebp-$10]
00512759 9B               wait 
Unit1.pas.37: for i:= 1 to m do
0051275A 8B45F8           mov eax,[ebp-$08]
0051275D 85C0             test eax,eax
0051275F 7E57             jle $005127b8
00512761 8945D0           mov [ebp-$30],eax
00512764 C745EC01000000   mov [ebp-$14],$00000001
Unit1.pas.39: t:= t + i / 999999.0;
0051276B DB45EC           fild dword ptr [ebp-$14]
0051276E D835E8275100     fdiv dword ptr [$005127e8]
00512774 DC45F0           fadd qword ptr [ebp-$10]
00512777 DD5DF0           fstp qword ptr [ebp-$10]
0051277A 9B               wait 
Unit1.pas.40: d:= t * t + i;
0051277B DD45F0           fld qword ptr [ebp-$10]
0051277E DC4DF0           fmul qword ptr [ebp-$10]
00512781 DB45EC           fild dword ptr [ebp-$14]
00512784 DEC1             faddp st(1)
00512786 DD5DE0           fstp qword ptr [ebp-$20]
00512789 9B               wait 
Unit1.pas.41: r:= (t + d) / (200000.0 * (i + 1));
0051278A DD45F0           fld qword ptr [ebp-$10]
0051278D DC45E0           fadd qword ptr [ebp-$20]
00512790 8B45EC           mov eax,[ebp-$14]
00512793 40               inc eax
00512794 8945CC           mov [ebp-$34],eax
00512797 DB45CC           fild dword ptr [ebp-$34]
0051279A D80DEC275100     fmul dword ptr [$005127ec]
005127A0 DEF9             fdivp st(1)
005127A2 DD5DD8           fstp qword ptr [ebp-$28]
005127A5 9B               wait 
Unit1.pas.42: t:= t - r;
005127A6 DD45F0           fld qword ptr [ebp-$10]
005127A9 DC65D8           fsub qword ptr [ebp-$28]
005127AC DD5DF0           fstp qword ptr [ebp-$10]
005127AF 9B               wait 
Unit1.pas.43: end;
005127B0 FF45EC           inc dword ptr [ebp-$14]
Unit1.pas.37: for i:= 1 to m do
005127B3 FF4DD0           dec dword ptr [ebp-$30]
005127B6 75B3             jnz $0051276b
Unit1.pas.44: end;
005127B8 FF45E8           inc dword ptr [ebp-$18]
Run Code Online (Sandbox Code Playgroud)

64位编译器

Unit1.pas.36: t:= t / 1000.0;
000000000059F94E F20F104548       movsd xmm0,qword ptr [rbp+$48]
000000000059F953 F20F5E05BD000000 divsd xmm0,qword ptr [rel $000000bd]
000000000059F95B F20F114548       movsd qword ptr [rbp+$48],xmm0
000000000059F960 C7C001000000     mov eax,$00000001
000000000059F966 8B5568           mov edx,[rbp+$68]
000000000059F969 894544           mov [rbp+$44],eax
000000000059F96C 395544           cmp [rbp+$44],edx
000000000059F96F 7F73             jnle xxx + $C4
000000000059F971 83C201           add edx,$01
Unit1.pas.39: t:= t + i / 999999.0;
000000000059F974 F20F2A4544       cvtsi2sd xmm0,dword ptr [rbp+$44]
000000000059F979 F20F5E059F000000 divsd xmm0,qword ptr [rel $0000009f]
000000000059F981 F20F104D48       movsd xmm1,qword ptr [rbp+$48]
000000000059F986 F20F58C8         addsd xmm1,xmm0
000000000059F98A F20F114D48       movsd qword ptr [rbp+$48],xmm1
Unit1.pas.40: d:= t * t + i;
000000000059F98F F20F104548       movsd xmm0,qword ptr [rbp+$48]
000000000059F994 F20F594548       mulsd xmm0,qword ptr [rbp+$48]
000000000059F999 F20F2A4D44       cvtsi2sd xmm1,dword ptr [rbp+$44]
000000000059F99E F20F58C1         addsd xmm0,xmm1
000000000059F9A2 F20F114538       movsd qword ptr [rbp+$38],xmm0
Unit1.pas.41: r:= (t + d) / (200000.0 * (i + 1));
000000000059F9A7 F20F104548       movsd xmm0,qword ptr [rbp+$48]
000000000059F9AC F20F584538       addsd xmm0,qword ptr [rbp+$38]
000000000059F9B1 8B4544           mov eax,[rbp+$44]
000000000059F9B4 83C001           add eax,$01
000000000059F9B7 F20F2AC8         cvtsi2sd xmm1,eax
000000000059F9BB F20F590D65000000 mulsd xmm1,qword ptr [rel $00000065]
000000000059F9C3 F20F5EC1         divsd xmm0,xmm1
000000000059F9C7 F20F114530       movsd qword ptr [rbp+$30],xmm0
Unit1.pas.42: t:= t - r;
000000000059F9CC F20F104548       movsd xmm0,qword ptr [rbp+$48]
000000000059F9D1 F20F5C4530       subsd xmm0,qword ptr [rbp+$30]
000000000059F9D6 F20F114548       movsd qword ptr [rbp+$48],xmm0
Unit1.pas.43: end;
000000000059F9DB 83454401         add dword ptr [rbp+$44],$01
000000000059F9DF 395544           cmp [rbp+$44],edx
000000000059F9E2 7590             jnz xxx + $54
000000000059F9E4 90               nop
Unit1.pas.44: end;
000000000059F9E5 83454001         add dword ptr [rbp+$40],$01
000000000059F9E9 394D40           cmp [rbp+$40],ecx
000000000059F9EC 0F855CFFFFFF     jnz xxx + $2E
000000000059F9F2 90               nop
Unit1.pas.45: writeln(t);
000000000059F9F3 488B0D9E150300   mov rcx,[rel $0003159e]
Run Code Online (Sandbox Code Playgroud)

奇怪的是,在这种情况下,x87 fpu代码实际上大约快了约5%.结论可能只是Delphi的32位/ x87编译器非常成熟并且相当好地优化的事实,64位编译器可能有一些空间来改进性能.我可以很容易地看到一些可以在这里优化SSE代码的地方; i例如,可以存储在XMM寄存器中并重复使用而不是每次重新转换cvtsi2sd,d可以保存在XMM寄存器中以进行下一次计算,而不是存储和重新加载等.

MOVXMM寄存器中的未对齐可以实际上非常昂贵.实际的SSE计算速度更快,但过多的数据移动可能会使分数缩小.也许Java强制堆栈上的16字节对齐?我知道MacOS会这样做,并且SSE使用对齐而非未对齐的移动有明显的好处(当然,代价是消耗更多的堆栈空间).

例如

  • fild :1 op,9延迟(x87)
  • cvtsi2sd :2 op,12延迟(SSE)

要么

  • fld :1 op,4延迟(x87)
  • movsd [r,m]:2op,4延迟(SSE)

Delphi的编译器在发出SSE指令的同时,似乎仍然以类似于使用x87单元的方式处理工作流,这不一定是最好的方法.在任何一种情况下,David都是正确的 - 编译器就是这样.你无法做任何改变它.

在我需要快速数学例程的地方,我仍然自己在ASM中编写代码 - 这通常优于任何编译器都可以执行的操作,因为您可以根据您正在进行的精确计算自定义行为.我有旧的传统32位应用程序,带有手动调整的SSE3 ASM算法,用于复数运算和矩阵运算.关键是你不需要优化所有东西 - 你只需要优化瓶颈.这是一个值得注意的重要一点.


Jer*_*ers 8

我将在这里回答元问题:"为什么Delphi编译器不能使用更现代的CPU指令,为什么Java可以?"

基本上,有两种编译代码的方法:

  1. 在开发者机器上预编译
  2. 在目标机器上进行后编译(包括JITed)

例子包括Delphi,C/C++等
.2的例子包括Java,.NET,JavaScript等.

预编译环境

预编译环境允许您编译一次代码,并在目标计算机上运行它.编译的程序无法在使用旧指令集的计算机上运行,​​而不是编译程序使用.最低要求是编译器可以做到的最低要求,也是所有目标计算机的最低架构.如果您不了解目标计算机,则受编译器的限制.

后编译环境

后编译环境在目标机器上编译.您不必知道它运行的体系结构:在其上运行的编译器需要知道它支持什么才能获得最大的好处.最低要求是编译器可以做到的最低要求,以及目标机器的体系结构.

原因是在后编译,JIT或解释语言环境中,编译器实际上在目标机器上运行.这意味着编译器可以使用该目标体系结构的所有功能.它甚至可以考虑物理内存,缓存大小或磁盘速度等方面,并测量当前运行时性能,以便对运行的代码进行编译后优化.

Delphi等工具

对于Windows 32位Delphi编译器,我认为最低要求仍然是486或Pentium(给定Pentium-Safe FDIV选项).因此,它使用x87作为CPU代码.
Windows 64位Delphi编译器具有SSE指令的最低要求,它采用了FPU代码.
我还没有检查其他编译器平台的最低要求.

Delphi的最低要求与强调向后兼容性有关.

其他一些环境(大多数C/++编译器,也可能是其他环境)允许您指定最小指令集.德尔福没有.我认为主要原因是开发和测试的复杂性.可能性的矩阵(如果确实是二维问题)很快变大.

JIT编译器通常不能全面支持最新的硬件架构优势,因为这样做非常昂贵.

JIT编译器通常支持某些处理器系列的优化(例如,在复制内存区域时).

我知道Java和.NET在过去十年中在这方面取得了一些进展.2005年有一篇关于.NET JIT使用CPU功能的文章.