Operator langsamer als Function

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut

Operator langsamer als Function

Beitragvon Mathias » 8. Jul 2018, 17:19 Operator langsamer als Function

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 gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3979
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon Warf » 8. Jul 2018, 18:28 Re: Operator langsamer als Function

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
Warf
 
Beiträge: 831
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Mathias » 8. Jul 2018, 21:35 Re: Operator langsamer als Function

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 gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3979
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon kupferstecher » 9. Jul 2018, 10:33 Re: Operator langsamer als Function

gelöscht
kupferstecher
 
Beiträge: 133
Registriert: 17. Nov 2016, 11:52

Beitragvon Warf » 9. Jul 2018, 12:07 Re: Operator langsamer als Function

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
Warf
 
Beiträge: 831
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Socke » 9. Jul 2018, 15:45 Re: Operator langsamer als Function

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
Socke
 
Beiträge: 2498
Registriert: 22. Jul 2008, 18:27
Wohnort: Köln
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 8.1/Debian GNU/Linux/Raspbian/openSUSE | 
CPU-Target: 32bit x86 armhf
Nach oben

Beitragvon Mathias » 9. Jul 2018, 17:25 Re: Operator langsamer als Function

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 gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3979
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon Warf » 9. Jul 2018, 20:05 Re: Operator langsamer als Function

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
Warf
 
Beiträge: 831
Registriert: 23. Sep 2014, 16:46
Wohnort: Aachen
OS, Lazarus, FPC: Mac OSX 10.11 | Win 10 | FPC 3.0.0 | L trunk | 
CPU-Target: x86_64, i368, ARM
Nach oben

Beitragvon Mathias » 10. Jul 2018, 16:32 Re: Operator langsamer als Function

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.
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Mit Lazarus sehe ich gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3979
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon Mathias » 10. Jul 2018, 16:58 Re: Operator langsamer als Function

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 gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3979
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon indianer-frank » 10. Jul 2018, 18:47 Re: Operator langsamer als Function

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.
indianer-frank
 
Beiträge: 130
Registriert: 30. Nov 2008, 21:53

Beitragvon Mathias » 10. Jul 2018, 19:18 Re: Operator langsamer als Function

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 gün
Mit Java und C/C++ sehe ich rot
Mathias
 
Beiträge: 3979
Registriert: 2. Jan 2014, 17:21
Wohnort: Schweiz
OS, Lazarus, FPC: Linux (die neusten Trunc) | 
CPU-Target: 64Bit
Nach oben

Beitragvon indianer-frank » 10. Jul 2018, 19:39 Re: Operator langsamer als Function

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.
indianer-frank
 
Beiträge: 130
Registriert: 30. Nov 2008, 21:53

• Themenende •

Zurück zu Freepascal



Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste

porpoises-institution
accuracy-worried