Delphi上64位平台上的汇编程序功能

时间:2014-07-23 20:05:32

标签: delphi assembly 64-bit inline-assembly

我有以下功能,需要使其与64位平台兼容:

procedure ExecuteAsm(Tab, Buf: Pointer; Len: DWORD);
asm
     mov   ebx, Tab
     mov   ecx, Len
     mov   edx, Buf
@1:  mov   al,  [edx]
     xlat
     mov   [edx], al
     inc   edx
     dec   ecx
     jnz @1
end;

Delphi XE5在[dcc64 Error] E2107 Operand size mismatchTab参数的行上引发错误Len

不幸的是,我不知道汇编程序是否足以自行解决问题。成功编译函数应该更改什么?

4 个答案:

答案 0 :(得分:5)

该汇编代码基本上只是执行以下操作,它可以在32位和64位中运行:

procedure ExecuteAsm(Tab, Buf: Pointer; Len: DWORD);
var
  pBuf: PByte;
begin
  pBuf := PByte(Buf);
  repeat
    pBuf^ := PByte(Tab)[pBuf^];
    Inc(pBuf);
    Dec(Len);
  until Len = 0;
end;

那么为什么不使用普通的Delphi代码并让编译器处理程序集呢?

答案 1 :(得分:5)

为什么使用汇编程序?

没有充分的理由!

这是你的asm代码直接转换为Delphi pascal:

procedure ExecuteAsm(Tab, Buf: PByte; Len: DWORD);
 repeat
   Buf^ := Tab[Buf^];
   inc(Buf);
   dec(Len);
 until Len = 0;
end;

但正如您现在所看到的,如果值Len为0,则程序应该对程序备忘录进行验证。

...

这段代码看起来更好,因为while循环测试0值而从不执行循环。

procedure ExecuteAsm(Tab, Buf: PByte; Len: cardinal);
begin
  while Len > 0 do
  begin
    Buf^ := Tab[Buf^];
    inc(Buf);
    dec(Len);
  end;
end;

但是,如果您仍然喜欢汇编程序,则必须保留ebx / rbx寄存器,如...

procedure ExecuteAsm(Tab, Buf: Pointer; Len: DWORD);
asm
    push    ebx   //rbx

//... your code

    pop     ebx   //rbx
end;

编辑:添加了32位和64位测试

因为HeartWare没有做David Heffernan的作业,所以我做到了。 最初的测试让David Heffernan看了看HeartWares的评论。我做了一些改动,又增加了两个测试用例。 该指令很重要:{$ O +} //打开编译器优化......:)

{$APPTYPE CONSOLE}

uses
  Diagnostics;

 {$O+} //Turn on compiler optimisation... :)

procedure _asm_GJ(Tab, Buf : PByte; Len : Cardinal);
//    32-bit   eax edx           ecx
//    64-bit   rcx rdx           r8
asm
{$IFDEF CPUX64 }
        test    Len, Len
        jz      @exit
@loop:
        movzx   rax, [Buf]
        mov     al, byte ptr[Tab + rax]
        mov     [Buf],al
        inc     Buf
        dec     Len
        jnz     @loop
{$ELSE }
        test    Len, Len
        jz      @exit
        push    ebx
@loop:
        movzx   ebx, [Buf]
        mov     bl,byte ptr[Tab + ebx]
        mov     [Buf], bl
        inc     Buf
        dec     Len
        jnz     @loop
        pop     ebx
{$ENDIF }
@exit:
end;

procedure _asm_HeartWare(Tab, Buf : PByte; Len : Cardinal);
//  32-bit     EAX EDX           ECX
//  64-bit     RCX RDX           R8
asm
    {$IFDEF CPUX64 }
        XCHG    R8,RCX
        JECXZ   @OUT
        XOR     RAX,RAX
    @LOOP:
        MOV     AL,[RDX]
        MOV     AL,[R8+RAX]
        MOV     [RDX],AL
        INC     RDX
        DEC     ECX
        JNZ     @LOOP
        // LOOP @LOOP
    {$ELSE }
        JECXZ   @OUT
        PUSH    EBX
        XCHG    EAX,EBX
        XOR     EAX,EAX
    @LOOP:
        MOV     AL,[EDX+ECX-1]
        MOV     AL,[EBX+EAX]
        MOV     [EDX+ECX-1],AL
        DEC     ECX
        JNZ     @LOOP
        // LOOP @LOOP
        POP     EBX
    {$ENDIF }
    @OUT:
end;

procedure _pas_normal(Tab, Buf: PByte; Len: Cardinal);
begin
  while Len > 0 do begin
    Buf^ := Tab[Buf^];
    inc(Buf);
    dec(Len);
  end;
end;

procedure _pas_inline(Tab, Buf: PByte; Len: Cardinal); inline;
begin
  while Len > 0 do begin
    Buf^ := Tab[Buf^];
    inc(Buf);
    dec(Len);
  end;
end;

var
  Stopwatch: TStopwatch;
  i: Integer;
  x, y: array [0 .. 1023] of Byte;

procedure refresh;
begin
  for i := low(x) to high(x) do
  begin
    x[i] := i mod 256;
    y[i] := (i + 20) mod 256;
  end;
end;

begin
{$IFDEF CPUX64 }
  Writeln('64 bit mode');
{$ELSE }
  Writeln('32 bit mode');
{$ENDIF }
  refresh;
  Stopwatch := TStopwatch.StartNew;
  for i := 1 to 1000000 do
  begin
    _asm_HeartWare(PByte(@x), PByte(@y), SizeOf(x));
  end;
  Writeln('asm HeartWare : ', Stopwatch.ElapsedMilliseconds, 'ms');

  refresh;
  Stopwatch := TStopwatch.StartNew;
  for i := 1 to 1000000 do
  begin
    _asm_GJ(PByte(@x), PByte(@y), SizeOf(x));
  end;
  Writeln('asm GJ        : ', Stopwatch.ElapsedMilliseconds, 'ms');

  refresh;
  Stopwatch := TStopwatch.StartNew;
  for i := 1 to 1000000 do
  begin
    _pas_normal(PByte(@x), PByte(@y), SizeOf(x));
  end;
  Writeln('pas normal    : ', Stopwatch.ElapsedMilliseconds, 'ms');

  refresh;
  Stopwatch := TStopwatch.StartNew;
  for i := 1 to 1000000 do
  begin
    _pas_inline(PByte(@x), PByte(@y), SizeOf(x));
  end;
  Writeln('pas inline    : ', Stopwatch.ElapsedMilliseconds, 'ms');

  Readln;
end.

结果......

enter image description here

... Cunclusion

几乎无话可说!数字谈话......

Delphi编译器很好,嗯非常好!

我已经内置了测试另一个asm优化程序,因为HeartWare asm优化并不是真正的优化。

答案 2 :(得分:3)

注意:阅读GJ接受的答案,因为它包含一个Pascal实现,打败了我的版本(我似乎混淆编译器使用ABSOLUTE来克服GJ的实现带来的签名问题,这是其中一个原因为什么我没有将它用作Pascal版本,但即使重新编码以匹配签名并在例程中使用显式类型转换,它仍然比我的Pascal版本快得多,并且与优化的汇编程序版本相同,所以在我自己的回复和其他所有内容中,尽可能使用Pascal实现,除非它是一个被称为无数次的时间关键例程,实际基准测试表明ASM版本明显更快 - (在我的辩护中)我的基准做了显示。

{$IFDEF MSWINDOWS }
PROCEDURE ExecuteAsm(Tab,Buf : POINTER ; Len : DWORD); ASSEMBLER; Register;
  //      32-bit     EAX EDX             ECX
  //      64-bit     RCX RDX             R8
  ASM
    {$IFDEF CPUX64 }
        XCHG    R8,RCX
        JECXZ   @OUT
        XOR     RAX,RAX
    @LOOP:
        MOV     AL,[RDX]
        MOV     AL,[R8+RAX]
        MOV     [RDX],AL
        INC     RDX
        DEC     ECX
        JNZ     @LOOP
        // LOOP @LOOP
    {$ELSE }
        JECXZ   @OUT
        PUSH    EBX
        XCHG    EAX,EBX
        XOR     EAX,EAX
    @LOOP:
        MOV     AL,[EDX+ECX-1]
        MOV     AL,[EBX+EAX]
        MOV     [EDX+ECX-1],AL
        DEC     ECX
        JNZ     @LOOP
        // LOOP @LOOP
        POP     EBX
    {$ENDIF }
    @OUT:
  END;
{$ELSE }
PROCEDURE ExecuteAsm(Tab,Buf : POINTER ; Len : DWORD);
  VAR
    TabP    : PByte ABSOLUTE Tab;
    BufP    : PByte ABSOLUTE Buf;
    I       : Cardinal;

  BEGIN
    FOR I:=1 TO Len DO BEGIN
      BufP^:=TabP[BufP^];
      INC(BufP)
    END
  END;
{$ENDIF }

这应该是所有当前支持的编译器和平台的有效替代。虽然我同意使用纯粹的Pascal版本可能会更好,但它确实会导致一些可怕的汇编代码以及大量不必要的寄存器重新加载(至少在32位中),因此纯汇编版本肯定更快。

然而,除非你像很多次一样运行它,否则在实际使用中你可能不会注意到它,纯粹的Pascal例程很可能会充分发挥作用。但是,只有您可以确定是否需要提高速度。

无论如何,这是在256字节阵列上执行PROCEDURE 100.000次的时间(使用XE5):

32-bit ASM: 47 ms
64-bit ASM: 47 ms
32-bit PAS: 63 ms
64-bit PAS: 78 ms

以及在RELEASE配置中运行10.000.000次的时间:

32-bit ASM: 5281 ms
64-bit ASM: 5281 ms
32-bit PAS: 7765 ms
64-bit PAS: 10031 ms

然而,在所有情况下,ASM版本都击败了Pascal版本......

手动优化的装配版本表现得更好:

32-bit ASM: 1906 ms
64-bit ASM: 1859 ms
32-bit PAS: 7781 ms
64-bit PAS: 10015 ms

用10.000倍25.600字节代替:

32-bit ASM: 218 ms
64-bit ASM: 172 ms
32-bit PAS: 734 ms
64-bit PAS: 937 ms

在所有情况下,我的ASM例程都胜过编译器的废话。我根本无法重现你的时间......你使用了什么代码和编译器?

计算时间的实际代码如下(对于10.000倍25.600字节):

T:=GetTickCount;
FOR I:=1 TO 10000 DO ExecuteAsm(TAB,BUF,25600);
T:=GetTickCount-T;

答案 3 :(得分:0)

绝对不确定它是否能正常工作但成功编译:

procedure ExecuteAsm(Tab, Buf: Pointer; Len: DWORD);
asm
     mov   rbx, Tab
     mov   ecx, Len
     mov   rdx, Buf
@1:  mov   al,  [rdx]
     xlat
     mov   [rdx], al
     inc   rdx
     dec   ecx
     jnz @1
end;

这是正确答案吗?