Operator langsamer als Function

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Operator langsamer als Function

Beitrag von Mathias »

Ich habe 2 ASM-Blöcke, die genau das selbe machen.

Code: Alles auswählen

function MatrixMultiplySSE(const M0, M1: Tmat4x4): Tmat4x4; assembler; nostackframe; register;
asm
         Movups  Xmm4, [M0 + $00]
         Movups  Xmm5, [M0 + $10]
         Movups  Xmm6, [M0 + $20]
         Movups  Xmm7, [M0 + $30]
 
         Xor     Ecx, Ecx
         @loop:
         Movss   Xmm0, [M1 + $00 + Ecx]
         Shufps  Xmm0, Xmm0, 00000000b
         Mulps   Xmm0, Xmm4
 
         Movss   Xmm2, [M1 + $04 + Ecx]
         Shufps  Xmm2, Xmm2, 00000000b
         Mulps   Xmm2, Xmm5
         Addps   Xmm0, Xmm2
 
         Movss   Xmm2, [M1 + $08 + Ecx]
         Shufps  Xmm2, Xmm2, 00000000b
         Mulps   Xmm2, Xmm6
         Addps   Xmm0, Xmm2
 
         Movss   Xmm2, [M1 + $0C + Ecx]
         Shufps  Xmm2, Xmm2, 00000000b
         Mulps   Xmm2, Xmm7
         Addps   Xmm0, Xmm2
 
         Movups  [Result + Ecx], Xmm0
 
         Add     Ecx, $10
         Cmp     Ecx, $30
         Jbe     @loop
end;
 
 
operator * (const M0, M1: Tmat4x4) Res: Tmat4x4; assembler; nostackframe; register;
asm
         Movups  Xmm4, [M0 + $00]
         Movups  Xmm5, [M0 + $10]
         Movups  Xmm6, [M0 + $20]
         Movups  Xmm7, [M0 + $30]
 
         Xor     Ecx, Ecx
         @loop:
         Movss   Xmm0, [M1 + $00 + Ecx]
         Shufps  Xmm0, Xmm0, 00000000b
         Mulps   Xmm0, Xmm4
 
         Movss   Xmm2, [M1 + $04 + Ecx]
         Shufps  Xmm2, Xmm2, 00000000b
         Mulps   Xmm2, Xmm5
         Addps   Xmm0, Xmm2
 
         Movss   Xmm2, [M1 + $08 + Ecx]
         Shufps  Xmm2, Xmm2, 00000000b
         Mulps   Xmm2, Xmm6
         Addps   Xmm0, Xmm2
 
         Movss   Xmm2, [M1 + $0C + Ecx]
         Shufps  Xmm2, Xmm2, 00000000b
         Mulps   Xmm2, Xmm7
         Addps   Xmm0, Xmm2
 
         Movups  [Res + Ecx], Xmm0
 
         Add     Ecx, $10
         Cmp     Ecx, $30
         Jbe     @loop
end;


Dazu habe ich folgenden Test-Code geschrieben:

Code: Alles auswählen

procedure TForm1.Button4x4Click(Sender: TObject);
var
  x, y, i: integer;
  ma, mb, mc, m0, m1: Tmat4x4;
  t: TTime;
const
  site = 20000001;
 
  procedure Ausgabe(m: Tmat4x4);
  var
    i: integer;
  begin
    for i := 0 to 3 do begin
      WriteLn(m[i, 0]: 4: 2, '  ', m[i, 1]: 4: 2, '  ', m[i, 2]: 4: 2, '  ', m[i, 3]: 4: 2);
    end;
    WriteLn();
  end;
 
  function GetZeit(z: TTime): string;
  begin
    str(z * 24 * 60 * 60: 10: 4, Result);
  end;
 
begin // Den beiden Quellmatrizen Werte zuweisen.
  for x := 0 to 3 do begin
    for y := 0 to 3 do begin
      m0[x, y] := x + y * 4;
      m1[x, y] := y + x * 4;
    end;
  end;
 
  t := now;
  for i := 0 to site do begin
    ma := m0 * m1;  // Operator
  end;
  WriteLn('Operator SSE:   ', GetZeit(now - t));
  Ausgabe(ma);
 
  t := now;
  for i := 0 to site do begin
    mc := MatrixMultiplySSE(m0, m1)// Funktion
  end;
  WriteLn('SSE neu:    ', GetZeit(now - t));
  Ausgabe(mc);
end;


Mit MatrixMultiplySSE läuft das ganze etwa 10% schneller als mit dem Operator. Wieso ?
Ist dies etwa Zufall ?
Ich habe es mehrmals probiert.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Operator langsamer als Function

Beitrag von Warf »

Ist das denn auf allen optimierungsstufen so?

Ich könnte mir vorstellen, da Operatoren ja mehr können als Funktionen (z.b. Operatoren Priorität) das beim generieren des call ein wenig overhead entsteht. Auf höheren optimierungsstufen könnte das wegfallen

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Operator langsamer als Function

Beitrag von Mathias »

Auf höheren optimierungsstufen könnte das wegfallen
Ich habe mal die Optimierung auf Stufe 4 gestellt, da werden beide Varianten ein wenig schneller.
Was ich noch probiert habe, ich habe den Operator und die Function getauscht. da wurde die Differenz ein wenig kleiner.
Was ich mir noch vorstellen kann, je nach dem wie der Code in die CPU kommt, gibt es Differenzen (L1 Cache) ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: Operator langsamer als Function

Beitrag von kupferstecher »

gelöscht

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Operator langsamer als Function

Beitrag von Warf »

Mathias hat geschrieben:
Auf höheren optimierungsstufen könnte das wegfallen
Ich habe mal die Optimierung auf Stufe 4 gestellt, da werden beide Varianten ein wenig schneller.
Was ich noch probiert habe, ich habe den Operator und die Function getauscht. da wurde die Differenz ein wenig kleiner.
Was ich mir noch vorstellen kann, je nach dem wie der Code in die CPU kommt, gibt es Differenzen (L1 Cache) ?


Ja am besten würde man sowas testen mit 2 verschiedenen Prozessen auf 2 verschiedenen CPUs ohne hyperthreading oder so Späße.

Cash locality kann richtig widerlich werden, so hatte ich schön öfter das ein Prozess schneller ging während nebenbei alle anredeten Prozessoren ausgelastet waren, gegenüber einem rein dafür dediziertem System, einfach weil das das OS Gezwungen hat den Prozess (und alle subpricesses) immer auf der selben CPU zu arbeiten

Am besten würde man das auf nem Rechner ohne Cache testen, sowas hat man aber eher selten noch rumfliegen

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Operator langsamer als Function

Beitrag von Socke »

Warf hat geschrieben:Am besten würde man das auf nem Rechner ohne Cache testen, sowas hat man aber eher selten noch rumfliegen

Wie wäre es denn mit dem exakten Gegenteil?
  • CPU-Zuordnung aktiv setzen
  • Ersten Lauf für Caching messen
  • Mind. zwei weitere Läufe inkl. Cache-Nutzung messen
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Operator langsamer als Function

Beitrag von Mathias »

Ich wollte den Code mit der Trunc von Lazarus/FPC laufen lassen.

Code: Alles auswählen

Access violation.
 
Press OK to ignore and risk data corruption.
Press Abort to kill the program.


Aber ich habe die Ursache gefunden, wen ich die Register ECX auf RCX umbenenne, geht es. Nur ist es dann nicht mehr kompatibel mit 32Bit.
FPC 3.0.4 hat die Register ECX selbständig auf RCX umbenannt.

Bei FPC 3.1.1 kann ich den ASM-Code nicht einsehen da "Access violation." schon kommt, bevor bevor überhaupt FormCreate aufgerufen wird.

Nachtrag: Kann es sein, das die Trunk keine Break-Points unterstützt ? ( Gelbes Fragezeichen im roten Kreis, links im Editor )
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Operator langsamer als Function

Beitrag von Warf »

Mathias hat geschrieben:Ich wollte den Code mit der Trunc von Lazarus/FPC laufen lassen.
Nachtrag: Kann es sein, das die Trunk keine Break-Points unterstützt ? ( Gelbes Fragezeichen im roten Kreis, links im Editor )


Debuggersymbole aktiviert?

Wenn ja probier doch per hand aus den Breakpoint zu bekommen:

Code: Alles auswählen

$ gdb execname
gdb> break filename:linenum
gdb> break functionname

und schau was er dir zurückgibt Wenn eventuell findet der GDB die Zeile nicht

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Operator langsamer als Function

Beitrag von Mathias »

Kann mal einer unter nativen Windows gucken, ob der Code im Anhang ohne "Access violation." läuft ?
Wieso läuft dies nicht mit Win64 ?

Unter Linux 64Bit läuft es ohne Probleme.

Ich habe es noch mit Linux 32Bit probiert, weil die RCX nicht kannte habe habe ich diese auf ECX umbenannt.
Somit kommt immerhin das Form mit dem 4x4 Button, aber sobald ich den Button drücke, kommt ein SIGSEV.

Dies habe ich alles mit FPC 3.0.4 und Lazarus 1.8.4 getestet.

Verwende ich die Trunc von FPC/Lazarus, dann kommt ein "Access violation." sogar bei der Linux 64Bit Version, sobald ich den Button drücke.
Ist mein ASM-Code falsch, oder hat FPC ein wenig Mühe mit den SSE-Registern ?

Debuggersymbole aktiviert?

Wenn ja probier doch per hand aus den Breakpoint zu

Da wohl Lazarus Trunk momentan Mühe, es geht nicht mal bei diesem Mini-Programm:

Code: Alles auswählen

program Project1;
begin
  WriteLn('Vorher');
  WriteLn('Nacher');
end.
Dateianhänge
42_-_SSE_Speedtest.tar.bz2
(79.7 KiB) 63-mal heruntergeladen
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Operator langsamer als Function

Beitrag von Mathias »

Nachtrag:
Mache ich es ohne Schleife mit ECX/RCX, dann geht es mit mit 32 und 64Bit, Win oder Linux, Stable und Trunk.
Was ist der Grund, wieso er Mühe mit ECX/RCX hat ?

Code: Alles auswählen

operator * (const M0, M1: Tmat4x4) Res: Tmat4x4; assembler; nostackframe; register;
asm
         Movups Xmm4, [M0 + $00]
         Movups Xmm5, [M0 + $10]
         Movups Xmm6, [M0 + $20]
         Movups Xmm7, [M0 + $30]
 
         // Spalte 0
         Movups Xmm2, [M1 + $00]
 
         Pshufd Xmm0, Xmm2, 00000000b
         Mulps  Xmm0, Xmm4
 
         Pshufd Xmm1, Xmm2, 01010101b
         Mulps  Xmm1, Xmm5
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 10101010b
         Mulps  Xmm1, Xmm6
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 11111111b
         Mulps  Xmm1, Xmm7
         Addps  Xmm0, Xmm1
 
         Movups [Result + $00], Xmm0
 
         // Spalte 1
         Movups Xmm2, [M1 + $10]
 
         Pshufd Xmm0, Xmm2, 00000000b
         Mulps  Xmm0, Xmm4
 
         Pshufd Xmm1, Xmm2, 01010101b
         Mulps  Xmm1, Xmm5
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 10101010b
         Mulps  Xmm1, Xmm6
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 11111111b
         Mulps  Xmm1, Xmm7
         Addps  Xmm0, Xmm1
 
         Movups   [Result + $10], Xmm0
 
         // Spalte 2
         Movups  Xmm2, [M1 + $20]
 
         Pshufd Xmm0, Xmm2, 00000000b
         Mulps  Xmm0, Xmm4
 
         Pshufd Xmm1, Xmm2, 01010101b
         Mulps  Xmm1, Xmm5
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 10101010b
         Mulps  Xmm1, Xmm6
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 11111111b
         Mulps  Xmm1, Xmm7
         Addps  Xmm0, Xmm1
 
         Movups [Result + $20], Xmm0
 
         // Spalte 3
         Movups Xmm2, [M1 + $30]
 
         Pshufd Xmm0, Xmm2, 00000000b
         Mulps  Xmm0, Xmm4
 
         Pshufd Xmm1, Xmm2, 01010101b
         Mulps  Xmm1, Xmm5
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 10101010b
         Mulps  Xmm1, Xmm6
         Addps  Xmm0, Xmm1
 
         Pshufd Xmm1, Xmm2, 11111111b
         Mulps  Xmm1, Xmm7
         Addps  Xmm0, Xmm1
 
         Movups [Result + $30], Xmm0
end;


Ich habe noch versucht am Ende folgendes einzufügen, bringt aber nichts. :roll:

Code: Alles auswählen

end ['rcx'];
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

indianer-frank
Beiträge: 134
Registriert: So 30. Nov 2008, 21:53

Re: Operator langsamer als Function

Beitrag von indianer-frank »

Wenn man sich das Assemblerlisting ansieht, erkennt man, daß RCX benutzt wird:

Code: Alles auswählen

# Var M0 located in register rdx
# Var M1 located in register r8
# Var $result located in register rcx 


Ändert man alle RCX der Schleifen-Version in RAX, dann ergibt sich bei mir

Code: Alles auswählen

C:\TMP>D:\FPC304\bin\i386-win32\ppcrossx64.exe -al op.pas
Free Pascal Compiler version 3.0.4 [2017/10/06] for x86_64
Copyright (c) 1993-2017 by Florian Klaempfl and others
Note: Switching assembler to default source writing assembler
Target OS: Win64 for x64
Compiling op.pas
op.pas(83,7) Note: Local variable "mb" not used
Assembling op
Linking op.exe
128 lines compiled, 0.2 sec, 73792 bytes code, 4964 bytes data
2 note(s) issued
 
C:\TMP>op.exe
Operator SSE:       0.2340
14.00  38.00  62.00  86.00
38.00  126.00  214.00  302.00
62.00  214.00  366.00  518.00
86.00  302.00  518.00  734.00
 
SSE neu:        0.2500
14.00  38.00  62.00  86.00
38.00  126.00  214.00  302.00
62.00  214.00  366.00  518.00
86.00  302.00  518.00  734.00 


Also zumindest rechnet er was und Operator ist minimal schneller auf meinen I3/2.6GHz. Dabei habe ich mangels Referenz (wo ist das deklariert) folgendes benutzt:

Code: Alles auswählen

 
type
 Tmat4x4=array[0..3, 0..3] of single; 


Also hat wohl etwas mit der Calling-Convention im 64-Bit-Modus zu tun, aber da kann ich auf die Schnelle auch nicht weiterhelfen.

Mathias
Beiträge: 6160
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Operator langsamer als Function

Beitrag von Mathias »

Also zumindest rechnet er was und Operator ist minimal schneller auf meinen I3/2.6GHz. Dabei habe ich mangels Referenz (wo ist das deklariert) folgendes benutzt:
Die Deklaration steckt in den Anhang im obigen Post.
Was noch interessant ist, mit Wine habe ich fast die gleichen Rechenzeiten wie mit nativen Linux. Somit ist Wine praktisch keine Bremse gegenüber Linux. Wie ist mit GUI aussieht, da könnte es anders sein.

Also hat wohl etwas mit der Calling-Convention im 64-Bit-Modus zu tun, aber da kann ich auf die Schnelle auch nicht weiterhelfen.

Immerhin läuft es jetzt auf allen Intel-Plattformen, auch wen der Code ein wenig grösser ist, dafür ist er ohne Schleife ein wenig schneller.

Assembler ist auch heute noch ein richtiges Abenteuer. 8)

Nachtrag:
Ich habe in einem fremden Code gerade Unterschiede gefunden:

Code: Alles auswählen

Function Max(Const A, B, C: Integer): Integer;   Assembler; Register;
asm
  {$IFDEF CPU64}
    {$IFDEF UNIX}
        MOV       EAX, EDI
        CMP       ESI, EAX
        CMOVG     EAX, ESI
        CMP       EDX, EAX
        CMOVG     EAX, EDX
    {$ELSE}
        MOV       RAX, RCX
        MOV       RCX, R8
        CMP       EDX, EAX
        CMOVG     EAX, EDX
        CMP       ECX, EAX
        CMOVG     EAX, ECX
    {$ENDIF}
  {$ELSE}
        CMP       EDX, EAX
        CMOVG     EAX, EDX
        CMP       ECX, EAX
        CMOVG     EAX, ECX
  {$ENDIF}
End;

Was ist überhaupt R8 ?
So ein Register kenne ich nur auf dem AVR.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

indianer-frank
Beiträge: 134
Registriert: So 30. Nov 2008, 21:53

Re: Operator langsamer als Function

Beitrag von indianer-frank »

Mathias hat geschrieben:Was ist überhaupt R8 ? So ein Register kenne ich nur auf dem AVR.
Siehe https://de.wikipedia.org/wiki/X64#Architektur, hier steht: Die gelb hinterlegten Register R8–R15 und XMM8–XMM15 stehen ausschließlich im 64-Bit-Modus zur Verfügung.

Antworten