Funktionen von Funktionsaufrufen in C ++

Vor nicht allzu langer Zeit hatte ich ein weiteres Gespräch mit einem Kollegen über ein ewiges Thema: "Nach Referenz oder nach Wert." Infolgedessen entstand dieser Artikel. Darin möchte ich die Ergebnisse meiner Forschung zu diesem und verwandten Themen vorstellen. Als nächstes wird betrachtet:


  • Register und deren Zweck beim Aufrufen von Funktionen.
  • Übertragung und Rückgabe einfacher Typen und Strukturen.
  • Wie sich die Übergabe nach Referenz und Wert auf die Optimierung des Funktionskörpers durch den Compiler auswirkt.
  • Wie Speicherplatz in mehreren Funktionsaufrufen verwendet wird.
  • Der Mechanismus virtueller Anrufe.
  • Optimierung von Tail Calls und Rekursion.
  • Initialisierung von Strukturen, Arrays und Vektoren.

Achtung Der Artikel enthält eine große Menge an C ++ - und Assembler-Code ( Intel ASM mit Kommentaren) sowie viele Tabellen mit Leistungsbewertungen. Alles, was geschrieben wurde, ist relevant für x86-64 System V ABI , das in allen modernen Unix-Betriebssystemen verwendet wird, z. B. Linux und macOS.


Die Informationen wurden aus dem System V Application Binary Interface für x86-64- Dokument entnommen. Assembler-Listen wurden für Clang 5.0.0 x86-64 mit den Flags -O3 -std=c++1z -march=sandybridge (unter Verwendung der Site https://godbolt.org ) erhalten. Leistungsbewertungen wurden für den Intel® Xeon® Prozessor E5-2660 mit 2,20 GHz vorgenommen .


Inhalt



Registriert sich in x86-64


Alle Daten werden im RAM gespeichert. Um die Arbeit damit zu beschleunigen, werden mehrstufige Caches verwendet. Um die Daten auf die eine oder andere Weise zu ändern, werden Register verwendet ( Diskussion in den Kommentaren ). Im Folgenden finden Sie eine sehr kurze Beschreibung der am häufigsten verwendeten Register in der x86-64-Architektur.


  • 16 Allzweckregister: rax, rbx, rcx, rdx, rbp, rsi, rdi, rsp und auch r8-r15 . Die Größe von jedem von ihnen beträgt 64 Bit (8 Bytes). Für den Zugriff auf die unteren 32 Bits (4 Bytes) e anstelle von r das Präfix e ( raxeax ) verwendet. Es werden nur Ganzzahlenoperationen unterstützt, die keine Vektoren sind.
  • rip (Befehlszeiger) gibt den Befehl an, der als nächstes ausgeführt werden soll. Verschiedene konstante Daten, die im Speicherabschnitt mit Anweisungen liegen, können mit einem Versatz relativ zum rip gelesen werden.
  • rsp (Stapelzeiger) zeigt auf das letzte Element auf dem Stapel. Der Stapel wächst zu niedrigeren Adressen. Wenn Sie etwas auf den Stapel schieben, wird der rsp Wert verringert .
  • 16 SSE-Register mit einer Größe von 128 Bit: xmm0 - xmm15 . Wenn der AVX Modus unterstützt wird, beziehen sie sich auf die unteren 128 Bit der ymm0 - ymm15 von denen jedes 256 Bit groß ist. Für Vektor- oder Nicht-Ganzzahloperationen müssen zuerst Daten in diese Register geladen werden.

Parameter übergeben


Dieser Abschnitt enthält eine etwas verkürzte und vereinfachte Beschreibung des Algorithmus zum Verteilen von Argumenten auf Register / Stapel. Eine vollständige Beschreibung finden Sie auf Seite 17 "System V ABI".


Wir stellen verschiedene Klassen von Objekten vor:


  • INTEGER - Integrale Typen in allgemeinen Registern. Dies sind bool , char , int und so weiter.
  • SSE sind Gleitkommazahlen, die in ein Vektorregister passen. Diese sind float und double .
  • MEMORY - Objekte, die den Stapel durchlaufen haben.

Um die Beschreibung zu vereinheitlichen, werden __int128 und complex Typen als Strukturen aus zwei Feldern dargestellt:


 struct __int128 { int64 low, high; }; struct complexT { T real, imag; }; //  T - float,  double. 

Zu Beginn wird jedes Funktionsargument klassifiziert:


  1. Wenn der Typ größer als 128 Bit ist oder nicht ausgerichtete Felder enthält, handelt es sich um MEMORY .
  2. Wenn es einen nicht trivialen Destruktor, einen Kopierkonstruktor, virtuelle Methoden und virtuelle Basisklassen gibt, wird dieser über einen "transparenten Link" weitergeleitet. Das Objekt wird durch einen Zeiger vom Typ INTEGER ersetzt .
  3. Aggregate, und dies sind Strukturen und Arrays, werden in 8-Byte- Stücken analysiert.
    1. Wenn das Stück ein Feld vom Typ MEMORY enthält , ist das gesamte Stück MEMORY .
    2. Wenn es ein Feld vom Typ INTEGER gibt , ist das ganze Stück INTEGER .
    3. Ansonsten das ganze Stück SSE .
  4. Wenn es ein Stück vom Typ MEMORY gibt , ist das gesamte Argument MEMORY .
  5. Die Typen long double und complex long double verwenden einen speziellen Satz von x87 FPU Registern und sind vom Typ MEMORY .
  6. Die __m256 , __m128 und __float128 sind vom Typ SSE .

Nach der Klassifizierung werden alle 8-Byte- Blöcke (in einem Block können mehrere Felder der Struktur oder Array-Elemente vorhanden sein) in Registern verteilt:


  1. Speicher werden durch den Stapel geleitet.
  2. INTEGERs werden in dieser Reihenfolge durch das nächste freie Register rdi, rsi, rdx, rcx, r8, r9 .
  3. SSEs werden über das nächste freie Register xmm0 - xmm7 .

Argumente werden von links nach rechts betrachtet. Die Argumente, die nicht genügend Register hatten, werden durch den Stapel geleitet. Wenn ein Teil des Arguments kein Register hatte, wird das gesamte Argument durch den Stapel geleitet.


Die Rückgabewerte lauten wie folgt:


  1. MEMORY- Typen werden über den Stack zurückgegeben. Der Platz darauf wird von der aufrufenden Funktion bereitgestellt und die Adresse ihres Anfangs wird durch rdi als wäre es das erste Argument für die Funktion. Bei der Rücksendung muss diese Adresse per rax . Das erste ursprüngliche Argument wird jeweils als zweites übergeben und so weiter.
  2. Der INTEGER- Block wird über das nächste freie Register rax, rdx .
  3. Der SSE- Block wird über das nächste freie Register xmm0, xmm1 . Diese Register werden sowohl zum Empfangen als auch zum Zurückgeben von Werten verwendet.

Eine Pivot-Tabelle mit Registern und deren Zweck ist beim Lesen von Assembler sehr nützlich:


RegistrierenTermin
raxTemporäres Register, geben Sie das erste (ret 1) INTEGER- Ergebnis zurück.
rbxGehört zur aufrufenden Funktion und darf zum Zeitpunkt der Rückgabe nicht geändert werden.
rcxÜbergabe des vierten (4) INTEGER- Arguments.
rdxÜbergeben Sie das dritte (3) INTEGER- Argument und geben Sie das zweite (ret 2) INTEGER- Ergebnis zurück.
rspZeiger auf den Stapel.
rbpGehört zur aufrufenden Funktion und darf zum Zeitpunkt der Rückgabe nicht geändert werden.
rsiÜbergabe des zweiten (2) INTEGER- Arguments.
rdiÜbergabe des ersten (1) INTEGER- Arguments.
r8Übergabe des fünften (5) INTEGER- Arguments.
r9Übergabe des sechsten (6) INTEGER- Arguments.
r10-r11Temporäre Register.
r12-r15Gehört zur aufrufenden Funktion und darf zum Zeitpunkt der Rückgabe nicht geändert werden.
xmm0-xmm1Übergeben Sie das erste und das zweite SSE- Argument und geben Sie es zurück.
xmm2-xmm7Übergabe des dritten bis sechsten SSE- Arguments.
xmm8-xmm15Temporäre Register.

Register, die zur aufrufenden Funktion gehören, sollten entweder nicht verwendet werden, oder ihre Werte müssen irgendwo, beispielsweise auf dem Stapel, gespeichert und dann wiederhergestellt werden.


Einfache Beispiele


Sofern nicht ausdrücklich anders angegeben, wurden alle verwendeten Funktionen als NOINLINE markiert. Wir geben vor, dass sich der Funktionskörper in der cpp-Datei befindet und LTO deaktiviert ist. Außerdem werden alle Funktionsergebnisse an eine leere NOINLINE Funktion übertragen, um zu verhindern, dass der Optimierer den gesamten Code löscht.


 #define NOINLINE __attribute__((noinline)) #define INLINE static __attribute__((always_inline)) 

Betrachten Sie etwas Einfaches.


 double foo(int8_t a, int16_t b, int32_t c, int64_t d, float x, double y) { return a + b + c + d + x + y; } ... auto result = foo(1, 2, 3, 4, 5, 6); 

Parameter werden wie folgt übergeben:


VornameRegistrierenVornameRegistrierenErgebnis
ardidrcxxmm0
brsixxmm0
crdxyxmm1

Betrachten Sie den generierten Code genauer.


 foo(signed char, short, int, long, float, double): add edi, esi #  a  b. add edi, edx #  c. movsxd rax, edi #    rax,    64    . add rax, rcx #  d. vcvtsi2ss xmm2, xmm2, rax #    float     xmm2. vaddss xmm0, xmm2, xmm0 #  x,  's'  vaddss    single precision. vcvtss2sd xmm0, xmm0, xmm0 #    double. vaddsd xmm0, xmm0, xmm1 #  y,  'd'  vaddsd    double precision. ret #       ,    ,    ,   rsp   8. .LCPI1_0: #    . .long 1084227584 # float 5 .LCPI1_1: .quad 4618441417868443648 # double 6 main: # @main sub rsp, 24 #     . vmovss xmm0, dword ptr [rip + .LCPI1_0] # xmm0 = mem[0],zero,zero,zero vmovsd xmm1, qword ptr [rip + .LCPI1_1] # xmm1 = mem[0],zero mov edi, 1 mov esi, 2 mov edx, 3 mov ecx, 4 call foo(signed char, short, int, long, float, double) # call      ,    rsp  8,      . vmovsd qword ptr [rsp + 16], xmm0 #     . 

Wenn Sie Parameter einfacher Typen an eine Funktion übergeben, müssen Sie sich bemühen, damit sie nicht durch Register geleitet werden.


Betrachten Sie verschiedene Beispiele für Aggregate. Arrays können als Strukturen mit mehreren Feldern betrachtet werden.


 struct St { double a, b; }; double foo(St s) { return sa + sb; } ... St s{1, 2}; auto result = foo(s); 

VornameRegistrierenVornameRegistrierenErgebnis
saxmm0jdnxmm1xmm0

Es scheint, dass nichts verhindert, zwei double in ein xmm Register zu xmm . Leider arbeitet der Verteilungsalgorithmus nur mit 8-Byte-Blöcken.


 foo(St): # @foo(St) vaddsd xmm0, xmm0, xmm1 #     ,       . ret .LCPI1_0: .quad 4607182418800017408 # double 1 .LCPI1_1: .quad 4611686018427387904 # double 2 main: # @main sub rsp, 24 #   . vmovsd xmm0, qword ptr [rip + .LCPI1_0] # xmm0 = mem[0],zero vmovsd xmm1, qword ptr [rip + .LCPI1_1] # xmm1 = mem[0],zero call foo(St) vmovsd qword ptr [rsp + 16], xmm0 #  double    . 

Wenn Sie ein weiteres double hinzufügen, wird die gesamte Struktur durch den Stapel geleitet, da ihre Größe 128 Byte überschreitet.


 struct St { double a, b, c; }; double foo(St s) { return sa + sb + sc; } ... St s{1, 2, 3}; auto result = foo(s); 

 foo(St): # @foo(St) #   ,    8 ,     .        ,         rsp+8. vmovsd xmm0, qword ptr [rsp + 8] # xmm0 = mem[0],zero vaddsd xmm0, xmm0, qword ptr [rsp + 16] vaddsd xmm0, xmm0, qword ptr [rsp + 24] ret .L_ZZ4mainE1s: .quad 4607182418800017408 # double 1 .quad 4611686018427387904 # double 2 .quad 4613937818241073152 # double 3 main: # @main sub rsp, 40 #    40 . #       ,    .      ,   mov       . mov rax, qword ptr [rip + .L_ZZ4mainE1s+16] #    '3'. mov qword ptr [rsp + 16], rax #  '3'  . vmovups xmm0, xmmword ptr [rip + .L_ZZ4mainE1s] #   xmm0  '1'  '2'. vmovups xmmword ptr [rsp], xmm0 #  '1'  '2  .    : 1 = *rsp , 2 = *(rsp+8), 3 = *(rsp+16). call foo(St) vmovsd qword ptr [rsp + 32], xmm0 #     double  . 

Mal sehen, was passiert, wenn wir double durch uint64_t ersetzen.


 struct St { uint64_t a, b; }; uint64_t foo(St s) { return sa + sb; } ... St s{1, 2}; auto result = foo(s); 

VornameRegistrierenVornameRegistrierenErgebnis
sardijdnrsirax

 foo(St): # @foo(St) lea rax, [rdi + rsi] ret main: # @main sub rsp, 24 mov edi, 1 mov esi, 2 call foo(St) mov qword ptr [rsp + 16], rax 

Das Ergebnis ist spürbar kompakter. Weitere Informationen darüber, warum die lea Anweisung anstelle von add verwendet wird, finden Sie beispielsweise hier: https://stackoverflow.com/a/6328441/1418863


Wenn Sie ein weiteres Feld hinzufügen, wird die Struktur wie im Beispiel mit double durch den Stapel geleitet. Der Code ist übrigens fast identisch, selbst das Laden auf den Stapel erfolgt über xmm Register.


Betrachten Sie etwas interessanteres.


 struct St { float a, b, c, d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2); 

VornameRegistrierenVornameRegistrierenErgebnis
s1.a.xmm0s1.b.xmm0xmm0, xmm1
s1.cxmm1s1.dxmm1
s2.a.xmm2s2.b.xmm2
s2.cxmm3s2.dxmm3

In jedes xmm Register werden zwei float Felder xmm .


 foo(St, St): # @foo(St, St) #   vaddps    float . vaddps xmm0, xmm0, xmm2 vaddps xmm1, xmm1, xmm3 ret .LCPI1_0: .long 1065353216 # float 1 .long 1073741824 # float 2 .zero 4 .zero 4 #   LCPI1_1 - LCPI1_3. ... main: # @main sub rsp, 24 #  ,      . vmovapd xmm0, xmmword ptr [rip + .LCPI1_0] # xmm0 = <1,2,u,u> vmovapd xmm1, xmmword ptr [rip + .LCPI1_1] # xmm1 = <3,4,u,u> vmovaps xmm2, xmmword ptr [rip + .LCPI1_2] # xmm2 = <5,6,u,u> vmovaps xmm3, xmmword ptr [rip + .LCPI1_3] # xmm3 = <7,8,u,u> call foo(St, St) #        ,         . xmm   256 ,    128    a  b,   - c  d. vunpcklpd xmm0, xmm0, xmm1 # xmm0 = xmm0[0],xmm1[0] vmovupd xmmword ptr [rsp + 8], xmm0 

Wenn die Struktur nicht 4, sondern drei Felder hätte, wäre der Funktionscode ähnlich, außer dass der zweite vaddps Befehl durch vaddss , der nur die ersten 64 Bits des Registers hinzufügt.


 struct St { int32_t a, b, c, d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2); 

VornameRegistrierenVornameRegistrierenErgebnis
s1.a.rdis1.b.rdirax, rdx
s1.crsis1.drsi
s2.a.rdxs2.b.rdx
s2.crcxs2.drcx

 foo(St, St): # @foo(St, St) lea eax, [rdx + rdi] movabs r8, -4294967296 # 0xFFFFFFFF00000000  . and rdi, r8 add rdi, rdx and rdi, r8 or rax, rdi lea edx, [rcx + rsi] #  ,   add. and rsi, r8 add rsi, rcx and rsi, r8 or rdx, rsi ret main: # @main sub rsp, 24 movabs rdi, 8589934593 #       . movabs rsi, 17179869187 movabs rdx, 25769803781 movabs rcx, 34359738375 call foo(St, St) mov qword ptr [rsp + 8], rax #    . mov qword ptr [rsp + 16], rdx 

Bitmagie passiert innerhalb der Funktion, aber das Prinzip ist ziemlich klar. Jedes Paar von 32-Bit-Zahlen wird in ein 64-Bit-Register gepackt. Rücksendungen erfolgen auf die gleiche Weise.


Mal sehen, was passiert, wenn wir anfangen, Feldtypen zu mischen, aber innerhalb von 8-Byte-Blöcken gehören sie zur selben Klasse.


 struct St { int32_t a, b; float c, d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2); 

VornameRegistrierenVornameRegistrierenErgebnis
s1.a.rdis1.b.rdirax, xmm0
s1.cxmm0s1.dxmm0
s2.a.rsis2.b.rsi
s2.cxmm1s2.dxmm1

 foo(St, St): # @foo(St, St) lea eax, [rsi + rdi] #     . movabs rcx, -4294967296 and rdi, rcx add rdi, rsi and rdi, rcx or rax, rdi vaddps xmm0, xmm0, xmm1 #    . ret .LCPI1_0: .long 1077936128 # float 3 .long 1082130432 # float 4 .zero 4 .zero 4 ... main: # @main sub rsp, 24 vmovaps xmm0, xmmword ptr [rip + .LCPI1_0] # xmm0 = <3,4,u,u> vmovaps xmm1, xmmword ptr [rip + .LCPI1_1] # xmm1 = <7,8,u,u> movabs rdi, 8589934593 movabs rsi, 25769803781 call foo(St, St) mov qword ptr [rsp + 8], rax #    . vmovlps qword ptr [rsp + 16], xmm0 

Dies ist jedoch nicht interessant, da die Feldtypen in jedem 8-Byte-Block gleich sind. Mische die Felder.


 struct St { int32_t a; float b; int32_t c; float d; }; St foo(St s1, St s2) { return {s1.a + s2.a, s1.b + s2.b, s1.c + s2.c, s1.d + s2.d}; } ... St s1{1, 2, 3, 4}, s2{5, 6, 7, 8}; auto result = foo(s1, s2); 

VornameRegistrierenVornameRegistrierenErgebnis
s1.a.rdis1.b.rdirax, rdx
s1.crsis1.drsi
s2.a.rdxs2.b.rdx
s2.crcxs2.drcx

Siehe Abschnitt 3.2. Da der 8-Byte-Block sowohl float als auch int , ist der gesamte Block vom Typ INTEGER und wird in allgemeinen Registern übergeben.


 foo(St, St): # @foo(St, St) mov rax, rdx add edx, edi shr rdi, 32 vmovd xmm0, edi mov rdi, rcx add ecx, esi shr rsi, 32 vmovd xmm1, esi shr rax, 32 vmovd xmm2, eax vaddss xmm0, xmm0, xmm2 shr rdi, 32 vmovd xmm2, edi vaddss xmm1, xmm1, xmm2 vmovd eax, xmm0 shl rax, 32 or rdx, rax vmovd eax, xmm1 shl rax, 32 or rcx, rax mov rax, rdx mov rdx, rcx ret main: # @main sub rsp, 24 movabs rdi, 4611686018427387905 # 0x4000000000000001,   32   int,   - float. movabs rsi, 4647714815446351875 movabs rdx, 4665729213955833861 movabs rcx, 4683743612465315847 call foo(St, St) mov qword ptr [rsp + 8], rax #      64  . mov qword ptr [rsp + 16], rdx 

Hier sehen Sie 6 Verschiebungsoperationen, um float Felder zu extrahieren und sie mit dem Ergebnis in das Register zu verschieben. Sowie das Fehlen jeglicher Vektoroperationen. Im Allgemeinen ist es am besten, die Feldtypen nicht innerhalb von 8-Byte-Blöcken der Struktur zu stören.


Pass per Link


Das Übergeben von Parametern über eine konstante Referenz ähnelt dem Übergeben eines Zeigers auf ein Objekt. Wenn das Objekt nicht in Register passt, wird es übergeben und durch den Stapel zurückgegeben. Mal sehen, wie das passiert. Betrachten Sie für den Realismus die Struktur für einen dreidimensionalen Punkt.


 struct Point3f { float x, y, z; }; struct Point3d { double x, y, z; }; Point3f scale(Point3f p) { return {px * 2, py * 2, pz * 2}; } Point3f scaleR(const Point3f& p) { return {px * 2, py * 2, pz * 2}; } Point3d scale(Point3d p) { return {px * 2, py * 2, pz * 2}; } Point3d scaleR(const Point3d& p) { return {px * 2, py * 2, pz * 2}; } 

Vergleichen Sie den Funktionscode. Meist werden neue xmm Register verwendet, daher ist die Logik verständlich.


 scale(Point3f): # @scale(Point3f) #     , x, y   xmm0, z - xmm1,     . vaddps xmm0, xmm0, xmm0 vaddss xmm1, xmm1, xmm1 ret scaleR(Point3f const&): # @scaleR(Point3f const&) #       rdi,     .      xmm0, xmm1. vmovsd xmm0, qword ptr [rdi] # xmm0 = mem[0],zero vaddps xmm0, xmm0, xmm0 vmovss xmm1, dword ptr [rdi + 8] # xmm1 = mem[0],zero,zero,zero vaddss xmm1, xmm1, xmm1 ret scale(Point3d): # @scale(Point3d) #   rdi  ,     .          [rsp+8, rsp+32).  [rsp, rsp+8)     . vmovapd xmm0, xmmword ptr [rsp + 8] vaddpd xmm0, xmm0, xmm0 vmovupd xmmword ptr [rdi], xmm0 vmovsd xmm0, qword ptr [rsp + 24] # xmm0 = mem[0],zero vaddsd xmm0, xmm0, xmm0 vmovsd qword ptr [rdi + 16], xmm0 mov rax, rdi #  ,    . ret scaleR(Point3d const&): # @scaleR(Point3d const&) #   ,      [rsp+8, rsp+32),   [rsi, rsi+24). vmovupd xmm0, xmmword ptr [rsi] vaddpd xmm0, xmm0, xmm0 vmovupd xmmword ptr [rdi], xmm0 vmovsd xmm0, qword ptr [rsi + 16] # xmm0 = mem[0],zero vaddsd xmm0, xmm0, xmm0 vmovsd qword ptr [rdi + 16], xmm0 mov rax, rdi ret 

Schauen wir uns nun den Ort an, an dem diese Funktionen aufgerufen werden.


 # scale(Point3f) main: # @main sub rsp, 24 #     . vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = <1,2,u,u> vmovss xmm1, dword ptr [rip + .LCPI4_1] # xmm1 = <3,u,u,u> call scale(Point3f) # scaleR(const Point3f&) main: # @main sub rsp, 24 #         rdi,     . mov edi, .L_ZZ4mainE1p # <1,2,3,u> call scaleR(Point3f const&) # scale(Point3d) main: # @main sub rsp, 64 #        . mov rax, qword ptr [rip + .L_ZZ4mainE1p+16] mov qword ptr [rsp + 16], rax vmovups xmm0, xmmword ptr [rip + .L_ZZ4mainE1p] vmovups xmmword ptr [rsp], xmm0 lea rbx, [rsp + 40] mov rdi, rbx #     [rsp+40, rsp+64). call scale(Point3d) .L_ZZ4mainE1p: .quad 4607182418800017408 # double 1 .quad 4611686018427387904 # double 2 .quad 4613937818241073152 # double 3 # scaleR(const Point3d&) main: # @main sub rsp, 64 #   . mov rax, qword ptr [rip + .L_ZZ4mainE1p+16] mov qword ptr [rsp + 32], rax vmovups xmm0, xmmword ptr [rip + .L_ZZ4mainE1p] vmovaps xmmword ptr [rsp + 16], xmm0 lea rbx, [rsp + 40] lea rsi, [rsp + 16] #     [rsp+16, rsp+40). mov rdi, rbx #     [rsp+40, rsp+64). call scaleR(Point3d const&) 

Mal sehen, ob wir viele Felder haben, aber die Struktur immer noch in die Register passt. Hier beginnt der Spaß.


 struct St { char d[16]; }; St foo(St s1, St s2) { //   s1  s2. St res; for(int i{}; i < 16; ++i) res.d[i] = s1.d[i] + s2.d[i]; return res; } 

Code für eine Funktion, die Argumente nach Wert akzeptiert.


VornameRegistrierenVornameRegistrierenErgebnis
s1.d [1: 8]rdis1.d [8:16]rsirax, rdx
s2.d [1: 8]rdxs2.d [8:16]rcx

 foo(St, St): # @foo(St, St) mov qword ptr [rsp - 16], rdi mov qword ptr [rsp - 8], rsi mov qword ptr [rsp - 32], rdx mov qword ptr [rsp - 24], rcx mov eax, edx add al, dil mov byte ptr [rsp - 48], al mov r8, rdi shr r8, 8 mov rax, rdx shr rax, 8 add al, r8b mov byte ptr [rsp - 47], al mov r8, rdi shr r8, 16 mov rax, rdx shr rax, 16 add al, r8b mov byte ptr [rsp - 46], al mov r8, rdi shr r8, 24 mov rax, rdx shr rax, 24 add al, r8b mov byte ptr [rsp - 45], al mov r8, rdi shr r8, 32 mov rax, rdx shr rax, 32 add al, r8b mov byte ptr [rsp - 44], al mov r8, rdi shr r8, 40 mov rax, rdx shr rax, 40 add al, r8b mov byte ptr [rsp - 43], al mov r8, rdi shr r8, 48 mov rax, rdx shr rax, 48 add al, r8b mov byte ptr [rsp - 42], al shr rdi, 56 shr rdx, 56 add dl, dil mov byte ptr [rsp - 41], dl mov eax, ecx add al, sil mov byte ptr [rsp - 40], al mov rax, rsi shr rax, 8 mov rdx, rcx shr rdx, 8 add dl, al mov byte ptr [rsp - 39], dl shr rsi, 16 shr rcx, 16 add cl, sil mov byte ptr [rsp - 38], cl mov al, byte ptr [rsp - 21] mov cl, byte ptr [rsp - 20] add al, byte ptr [rsp - 5] mov byte ptr [rsp - 37], al add cl, byte ptr [rsp - 4] mov byte ptr [rsp - 36], cl mov al, byte ptr [rsp - 19] mov cl, byte ptr [rsp - 18] add al, byte ptr [rsp - 3] mov byte ptr [rsp - 35], al add cl, byte ptr [rsp - 2] mov byte ptr [rsp - 34], cl mov al, byte ptr [rsp - 17] add al, byte ptr [rsp - 1] mov byte ptr [rsp - 33], al mov rax, qword ptr [rsp - 48] mov rdx, qword ptr [rsp - 40] ret 

Ja, hier werden alle Argumente auf den Stapel kopiert. Anschließend werden sie extrahiert und jeweils byteweise hinzugefügt. Wie Sie sehen können, enthält die Funktion genau 16 Anweisungen zum add . GCC erzeugt in diesem Beispiel übrigens einen viel kompakteren Code, aber immer noch mit Kopieren durch den Stapel. Kann etwas verbessert werden? Übergeben Sie die Struktur als Referenz.


 St fooR(const St& s1, const St& s2) { /*  . */ } 

VornameRegistrierenVornameRegistrierenErgebnis
s1rdis2rsirax, rdx

 fooR(St const&, St const&): # @fooR(St const&, St const&) vmovdqu xmm0, xmmword ptr [rsi] vpaddb xmm0, xmm0, xmmword ptr [rdi] vmovdqa xmmword ptr [rsp - 24], xmm0 mov rax, qword ptr [rsp - 24] mov rdx, qword ptr [rsp - 16] ret 

Oh ja! Es sieht viel besser aus. Wir können 16 xmm -Elemente gleichzeitig in das xmm Register xmm und vpaddb wodurch alle in einer Operation vpaddb werden. Danach wird das Ergebnis über den Stapel in die Ausgaberegister kopiert. Sie könnten denken, dass Sie diese letzte Operation loswerden können, indem Sie das erste Argument durch eine nicht konstante Referenz ersetzen.


 void fooR1(St &s1, const St& s2) { for(int i{}; i < 16; ++i) s1.d[i] += s2.d[i]; } 

VornameRegistrierenVornameRegistrierenErgebnis
s1rdis2rsi

 fooR1(St&, St const&): # @fooR1(St&, St const&) mov al, byte ptr [rsi] add byte ptr [rdi], al mov al, byte ptr [rsi + 1] add byte ptr [rdi + 1], al mov al, byte ptr [rsi + 2] add byte ptr [rdi + 2], al mov al, byte ptr [rsi + 3] add byte ptr [rdi + 3], al mov al, byte ptr [rsi + 4] add byte ptr [rdi + 4], al mov al, byte ptr [rsi + 5] add byte ptr [rdi + 5], al mov al, byte ptr [rsi + 6] add byte ptr [rdi + 6], al mov al, byte ptr [rsi + 7] add byte ptr [rdi + 7], al mov al, byte ptr [rsi + 8] add byte ptr [rdi + 8], al mov al, byte ptr [rsi + 9] add byte ptr [rdi + 9], al mov al, byte ptr [rsi + 10] add byte ptr [rdi + 10], al mov al, byte ptr [rsi + 11] add byte ptr [rdi + 11], al mov al, byte ptr [rsi + 12] add byte ptr [rdi + 12], al mov al, byte ptr [rsi + 13] add byte ptr [rdi + 13], al mov al, byte ptr [rsi + 14] add byte ptr [rdi + 14], al mov al, byte ptr [rsi + 15] add byte ptr [rdi + 15], al ret 

Etwas scheint schief gelaufen zu sein. Dies geschah, weil der Compiler standardmäßig sehr vorsichtig ist und darauf hinweist, dass der Programmierer möglicherweise nicht in Ordnung ist und so etwas schreibt:


 char buff[17]; fooR1(*reinterpret_cast<St*>(buff+1), reinterpret_cast<const St*>(buff)); 

In diesem Fall wird buff[i+1] += buff[i] bei jeder Iteration berechnet, dh Zeiger-Aliasing ist verfügbar. Um dem Compiler anzuzeigen, dass eine solch seltsame Verwendung der Funktion nicht erwartet wird, existiert das Schlüsselwort __restrict .


 void fooR2(St & __restrict s1, const St& s2) { /*  . */ } 

Welches ergibt das gewünschte Ergebnis.


 fooR2(St&, St const&): # @fooR2(St&, St const&) vmovdqu xmm0, xmmword ptr [rdi] vpaddb xmm0, xmm0, xmmword ptr [rsi] vmovdqu xmmword ptr [rdi], xmm0 ret 

void fooR3(St &__restrict s1, St s2) , , St foo(St, St) .


, , void foo(char* __restrict s1, const char* s2, int size) , __restrict .



b a , , foo :


 St a, b; st(a, b); // st(St& a, St& b) { a = b = {}; }   . a = foo(a, b); a = foo(a, b); a = foo(a, b); a = foo(a, b); 

CodeCycles per iteration
St a, b; st(a, b);7.6
4 x foo no reuse121.9
4 x foo117.7
4 x fooR no reuse66.3
4 x fooR64.6
4 x fooR184.5
4 x fooR220.6
4 x foo inline51.9
4 x fooR inline30.5
4 x fooR1 inline8.8
4 x fooR2 inline8.8

'no reuse' , . auto a2 = foo(a, b); auto a3 = foo(a2, b); . 'inline' , INLINE , NOINLINE .


fooR1 inline / fooR2 inline , , , , , foo inline / fooR inline , . , , .



, , .


 struct Point3f { float x, y, z; ~Point3f() {} }; Point3f scale(Point3f p) { return {px * 2, py * 2, pz * 2}; } 

rdi , . , rsi , .


 scale(Point3f): # @scale(Point3f) vmovss xmm0, dword ptr [rsi] # xmm0 = mem[0],zero,zero,zero vaddss xmm0, xmm0, xmm0 vmovss dword ptr [rdi], xmm0 vmovss xmm0, dword ptr [rsi + 4] # xmm0 = mem[0],zero,zero,zero vaddss xmm0, xmm0, xmm0 vmovss dword ptr [rdi + 4], xmm0 vmovss xmm0, dword ptr [rsi + 8] # xmm0 = mem[0],zero,zero,zero vaddss xmm0, xmm0, xmm0 vmovss dword ptr [rdi + 8], xmm0 mov rax, rdi ret 

, , ( ) . POD . Point3f scaleR(const Point3f&) . .


 Point3f p{1, 2, 3}; auto result = scale(p); sink(&result); 

 main: # @main push rbx sub rsp, 48 movabs rax, 4611686019492741120 #    . mov qword ptr [rsp + 16], rax mov dword ptr [rsp + 24], 1077936128 lea rbx, [rsp + 32] lea rsi, [rsp + 16] #    [rsp+16, rsp+28) mov rdi, rbx #    [rsp+32, rsp+44) call scale(Point3f) mov qword ptr [rsp + 8], rbx #  [rsp+8, rsp+16)    . lea rdi, [rsp + 8] call void sink<Point3f*>(Point3f* const&) xor eax, eax add rsp, 48 pop rbx ret #  . mov rdi, rax call _Unwind_Resume 

NOINLINE , .


 main: # @main push r14 push rbx sub rsp, 56 movabs rax, 4611686019492741120 #    [rsp, rsp+12).   p. mov qword ptr [rsp], rax mov dword ptr [rsp + 8], 1077936128 #    [rsp+24, rsp+36),    pTmp. mov eax, dword ptr [rsp + 8] mov dword ptr [rsp + 32], eax mov rax, qword ptr [rsp] mov qword ptr [rsp + 24], rax lea r14, [rsp + 40] lea rbx, [rsp + 24] mov rdi, r14 #    [rsp+40, rsp+52),  result. mov rsi, rbx #   -   pTmp. call scale(Point3f) mov rdi, rbx #    pTmp.    -  this. call Point3f::~Point3f() mov qword ptr [rsp + 16], r14 #  [rsp+16, rsp+24)     result.     sink. lea rdi, [rsp + 16] call void sink<Point3f*>(Point3f* const&) lea rdi, [rsp + 40] #    result. call Point3f::~Point3f() mov rdi, rsp #    p. call Point3f::~Point3f() xor eax, eax add rsp, 56 pop rbx pop r14 ret #  .           . mov rbx, rax lea rdi, [rsp + 40] #    result. call Point3f::~Point3f() mov rdi, rsp #    p. call Point3f::~Point3f() mov rdi, rbx call _Unwind_Resume 

p , .



, . .


 # Point3f result = scale(scale(Point3f{1, 2, 3})); sub rsp, 24 vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = <1,2,u,u> vmovss xmm1, dword ptr [rip + .LCPI4_1] # xmm1 = mem[0],zero,zero,zero #    xmm0, xmm1    ,    ,       ! call scale(Point3f) call scale(Point3f) vmovlps qword ptr [rsp + 8], xmm0 vmovss dword ptr [rsp + 16], xmm1 # Point3f result = scaleR(scaleR(Point3f{1, 2, 3})); sub rsp, 56 #       [rsp+24, rsp+36). movabs rax, 4611686019492741120 # 0x400000003F800000 = [2.0f, 1.0f] mov qword ptr [rsp + 24], rax mov dword ptr [rsp + 32], 1077936128 # 0x40400000 = 3.0f lea rdi, [rsp + 24] #      . call scaleR(Point3f const&) #    [rsp+8, rsp+20). vmovlps qword ptr [rsp + 8], xmm0 vmovss dword ptr [rsp + 16], xmm1 lea rdi, [rsp + 8] #       . call scaleR(Point3f const&) vmovlps qword ptr [rsp + 40], xmm0 vmovss dword ptr [rsp + 48], xmm1 

, , . , .


 # Point3d result = scale(scale(Point3d{1, 2, 3})); sub rsp, 112 #    [rsp, rsp+24). vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] # xmm0 = [1.000000e+00,2.000000e+00] vmovaps xmmword ptr [rsp + 32], xmm0 movabs rax, 4613937818241073152 # 0x4008000000000000 = 3.0 mov qword ptr [rsp + 48], rax mov rax, qword ptr [rsp + 48] mov qword ptr [rsp + 16], rax vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rsp], xmm0 #    [rsp+64, rsp+88). lea rdi, [rsp + 64] #  rdi     . call scale(Point3d) #        [rsp, rsp+24). mov rax, qword ptr [rsp + 80] #  z   z . mov qword ptr [rsp + 16], rax vmovups xmm0, xmmword ptr [rsp + 64] #  [x, y]   [x, y] . vmovups xmmword ptr [rsp], xmm0 #    [rsp+88, rsp+112). lea rbx, [rsp + 88] mov rdi, rbx call scale(Point3d) # Point3d result = scaleR(scaleR(Point3d{1, 2, 3})); sub rsp, 72 #    [rsp, rsp+24),   . vmovaps xmm0, xmmword ptr [rip + .LCPI4_0] vmovaps xmmword ptr [rsp], xmm0 movabs rax, 4613937818241073152 mov qword ptr [rsp + 16], rax lea r14, [rsp + 24] mov rsi, rsp #   -       [rsp, rsp+24). mov rdi, r14 #   -      [rsp+24, rsp+48). call scaleR(Point3d const&) lea rbx, [rsp + 48] mov rdi, rbx #      [rsp+48, rsp+72). mov rsi, r14 #       [rsp+24, rsp+48). call scaleR(Point3d const&) 

, , , , . – . . , , .



, . , . , , Point3f , Point3d – .


 //   data.cpp.  . Point3f pf() { return {1, 2, 3}; } Point3d pd() { return {1, 2, 3}; } 

CodeCycles per iteration
auto r = pf();6.7
auto r = scale(pf());11.1
auto r = scaleR(pf());12.6
auto r = scale(scale(pf()));18.2
auto r = scaleR(scaleR(pf()));18.3
auto r = scale(scale(scale(pf())));16.8
auto r = scaleR(scaleR(scaleR(pf())));20.2
auto r = pd();7.3
auto r = scale(pd());11.7
auto r = scaleR(pd());11.0
auto r = scale(scale(pd()));16.9
auto r = scaleR(scaleR(pd()));14.1
auto r = scale(scale(scale(pd())));21.2
auto r = scaleR(scaleR(scaleR(pd())));17.2
INLINE8.1 — 8.9

Point3f struct Point3i { int32_t x, y, z; }; Point3d struct Point3ll { int64_t x, y, z; }; , . , , , 64 int, . , Point3f struct Point2ll { int64_t x, y; }; Point3d struct Point4ll { int64_t x, y, z, a; }; , -.


:


  • . .
  • , , .
  • inline , . , , , . .

optional


std::optional , boost::optional , , "x86-64 clang (experimental concepts)" , , MSVC ,


 struct Point { float x, y; }; using OptPoint1 = optional<Point>; 


 struct OptPoint2 { float x, y; union { char _; bool d; }; //   std::optional. }; 

, OptPoint1 , OptPoint2 – .


 OptPoint1 foo(OptPoint1 s) { return Point{s->x + 1, s->y + 1}; } OptPoint2 foo(OptPoint2 s) { return {sx + 1, sy + 1, true}; } ... OptPoint1 s1{Point{1, 2}}; OptPoint2 s2{3, 4, true}; auto result1 = foo(s1); auto result2 = foo(s2); 

 .LCPI0_0: .long 1065353216 # float 1 foo(std::optional<Point>): # @foo(std::optional<Point>) vmovss xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero #  rsi       . vaddss xmm1, xmm0, dword ptr [rsi] #  x  ,  1. vaddss xmm0, xmm0, dword ptr [rsi + 4] #  y  ,  1. vmovss dword ptr [rdi], xmm1 #  x  , rdi      ,     . vmovss dword ptr [rdi + 4], xmm0 #  y  . mov byte ptr [rdi + 8], 1 #  optional::has_value(). mov rax, rdi #    . ret .LCPI1_0: .long 1065353216 # float 1 .long 1065353216 # float 1 .zero 4 .zero 4 foo(OptPoint2): # @foo(OptPoint2) vaddps xmm0, xmm0, xmmword ptr [rip + .LCPI1_0] #  [x, y] c [1, 1].  xmm0  . mov al, 1 #  d, al -   8   rax. ret .LCPI2_0: .long 1077936128 # float 3 .long 1082130432 # float 4 .zero 4 .zero 4 main: # @main push rbx sub rsp, 64 movabs rax, 4611686019492741120 # 0x400000003F800000  x  y. mov qword ptr [rsp + 32], rax #  x, y  . mov byte ptr [rsp + 40], 1 #  optional::has_value(). lea rbx, [rsp + 48] lea rsi, [rsp + 32] #    . mov rdi, rbx #    . call foo(std::optional<Point>) #     . vmovaps xmm0, xmmword ptr [rip + .LCPI2_0] # xmm0 = <3,4,u,u> mov edi, 1 #  bool d. call foo(OptPoint2) vmovlps qword ptr [rsp + 16], xmm0 #    . mov byte ptr [rsp + 24], al #  al      rax. 

, foo inline , .


  # OptPoint1 foo(OptPoint1)  OptPoint1 foo(const OptPoint1&) vmovss xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero vaddss xmm1, xmm0, dword ptr [rsp + 32] vaddss xmm0, xmm0, dword ptr [rsp + 36] vmovss dword ptr [rsp + 8], xmm1 vmovss dword ptr [rsp + 12], xmm0 mov byte ptr [rsp + 16], 1 # OptPoint2 foo(OptPoint2)  OptPoint2 foo(const OptPoint2&) vmovsd xmm0, qword ptr [rsp + 48] # xmm0 = mem[0],zero vaddps xmm0, xmm0, xmmword ptr [rip + .LCPI0_1] vmovlps qword ptr [rsp + 8], xmm0 mov byte ptr [rsp + 16], 1 

, , , .


: inline , std::optional .



, , . . , . , - , , , . .


 struct Fn { virtual ~Fn() noexcept = default; virtual int call(int x) const = 0; }; struct Add final : Fn { Add(int a) : a(a) {} int call(int x) const override { return a + x; } int a; }; NOINLINE bool isFixedPoint(const Fn& fn, int x) { return fn.call(x) == x; } int main() { Add add{32}; bool result = isFixedPoint(add, 10); } 

- .


 Add::call(int) const: # @Add::call(int) const #  rdi   this  ,   [rdi, rdi+8)      ,       . add esi, dword ptr [rdi + 8] mov eax, esi #    rax, eax     32 . ret #      Add.       16,   RTTI    . vtable for Add: .quad 0 .quad typeinfo for Add #   RTTI.  -8. .quad Fn::~Fn() # ,  0    . .quad Add::~Add() .quad Add::call(int) const # ,  16 . isFixedPoint(Fn const&, int): # @isFixedPoint(Fn const&, int) push rbx #         rbx,    ,    . mov ebx, esi #  32   . mov rax, qword ptr [rdi] #  rdi    -   Fn,         this . call qword ptr [rax + 16] #  Add::call. cmp eax, ebx #   call    rax,    ebx,      . sete al #     8   eax. pop rbx #   rbx. ret main: # @main sub rsp, 40 mov qword ptr [rsp + 24], vtable for Add+16 #          Add,    16  ,    , ,  .    RTTI    . mov dword ptr [rsp + 32], 32 #  add.a   . lea rdi, [rsp + 24] #      . mov esi, 10 #   . call isFixedPoint(Fn const&, int) mov byte ptr [rsp + 15], al #    . ... mov rdi, rax #  . call _Unwind_Resume mov rdi, rax call _Unwind_Resume 

protected , ( 34-37). NOINLINE , ( false ). NOINLINE , . .


 struct Add final { Add(int a) : a(a) {} NOINLINE int call(int x) const { return a + x; } int a; }; template<typename T> NOINLINE bool isFixedPoint(const T& fn, int x) { return fn.call(x) == x; } 

 Add::call(int) const: # @Add::call(int) const add esi, dword ptr [rdi] #    , this   rdi  ,        ,      . mov eax, esi ret bool isFixedPoint<Add>(Add const&, int): # @bool isFixedPoint<Add>(Add const&, int) push rbx mov ebx, esi # Add::call         ,   isFixedPoint. call Add::call(int) const cmp eax, ebx sete al pop rbx ret main: # @main sub rsp, 24 mov dword ptr [rsp + 8], 32 #  add.a. lea rdi, [rsp + 8] #    ,  rdi    add.a. mov esi, 10 call bool isFixedPoint<Add>(Add const&, int) mov byte ptr [rsp + 7], al ... ret 

, NOINLINE .



1000 Add isFixedPoint .


CodeCycles per iteration
call , isFixedPoint call5267
call , NOINLINE isFixedPoint10721
call , INLINE isFixedPoint8291
call , NOINLINE isFixedPoint10571
, NOINLINE call , NOINLINE isFixedPoint10536
, isFixedPoint call4505
, INLINE call , INLINE isFixedPoint4531

:


  • .
  • , .
  • , , , inline. .
  • inline . inline - cpp , NOINLINE .
  • inline .


call , jmp .


. clang . , :


 double exp_by_squaring(double x, int n, double y = 1) { if (n < 0) return exp_by_squaring(1.0 / x, -n, y); if (n == 0) return y; if (n == 1) return x * y; if (n % 2 == 0) return exp_by_squaring(x * x, n / 2, y); return exp_by_squaring(x * x, (n - 1) / 2, x * y); } 

Wir bekommen:


 .LCPI0_0: .quad 4607182418800017408 # double 1 exp_by_squaring(double, int, double): # @exp_by_squaring(double, int, double) vmovsd xmm2, qword ptr [rip + .LCPI0_0] # xmm2 = mem[0],zero vmovapd xmm3, xmm0 test edi, edi jns .LBB0_4 jmp .LBB0_3 .LBB0_9: # in Loop: Header=BB0_4 Depth=1 shr edi vmovapd xmm3, xmm0 test edi, edi jns .LBB0_4 .LBB0_3: # =>This Inner Loop Header: Depth=1 vdivsd xmm3, xmm2, xmm3 neg edi test edi, edi js .LBB0_3 .LBB0_4: # =>This Inner Loop Header: Depth=1 je .LBB0_7 cmp edi, 1 je .LBB0_6 vmulsd xmm0, xmm3, xmm3 test dil, 1 je .LBB0_9 lea eax, [rdi - 1] shr eax, 31 lea edi, [rdi + rax] add edi, -1 sar edi vmulsd xmm1, xmm3, xmm1 vmovapd xmm3, xmm0 test edi, edi jns .LBB0_4 jmp .LBB0_3 .LBB0_6: vmulsd xmm1, xmm3, xmm1 .LBB0_7: vmovapd xmm0, xmm1 ret 

, , . , , . ~10% .


. .


 int64_t sum(int64_t x, int64_t y) { return x + y; } int64_t add1(int64_t x) { return sum(x, 1); } int64_t add2(int64_t x) { return sum(1, x); } int64_t add3(int64_t x) { return sum(-1, x) + 2; } 

 sum(long, long): # @sum(long, long) lea rax, [rdi + rsi] ret add1(long): # @add1(long) mov esi, 1 #   . jmp sum(long, long) # TAILCALL add2(long): # @add2(long) mov rax, rdi #   . mov edi, 1 mov rsi, rax jmp sum(long, long) # TAILCALL add3(long): # @add3(long) push rax #  rax   . mov rax, rdi #  ,    add2,  . mov rdi, -1 mov rsi, rax call sum(long, long) add rax, 2 #  2    . pop rcx ret 

, , call , jmp . , , sum , , add .


, 10%.


:


  • , .
  • .


. 2D :


 struct Point { double x, y; }; struct ZeroPoint { double x{}, y{}; }; struct NanPoint { double x{quietNaN}, y{quietNaN}; }; 

Point . ZeroPoint . IEEE 754-1985:


The number zero is represented specially: sign = 0 for positive zero, 1 for negative zero; biased exponent = 0; fraction = 0;

memset . NanPoint numeric_limits<double>::quiet_NaN(); , .



 Point data; 

  sub rsp, 24 

- .


 ZeroPoint data; Point data{}; 

.


  sub rsp, 40 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp + 16], xmm0 

. xmm0 . XOR vxorps . .


 NanPoint data; 

  sub rsp, 40 vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] vmovaps xmmword ptr [rsp + 16], xmm0 

, .



:


 static constexpr size_t smallSize = 8; static constexpr size_t bigSize = 321; extern size_t smallUnknownSize; // Also 8 extern size_t bigUnknownSize; // Also 321 

.


 array<Point, smallSize> data; 

– .


  sub rsp, 136 

, .


 array<ZeroPoint, smallSize> data; array<ZeroPoint, smallSize> data{}; array<Point, smallSize> data{}; 

.


  sub rsp, 192 vxorps ymm0, ymm0, ymm0 vmovaps ymmword ptr [rsp + 128], ymm0 vmovaps ymmword ptr [rsp + 96], ymm0 vmovaps ymmword ptr [rsp + 64], ymm0 vmovaps ymmword ptr [rsp + 32], ymm0 

256 , 2 .


 array<NanPoint, smallSize> data; array<NanPoint, smallSize> data{}; 

  sub rsp, 136 vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] vmovups xmmword ptr [rsp + 24], xmm0 vmovups xmmword ptr [rsp + 8], xmm0 vmovups xmmword ptr [rsp + 56], xmm0 vmovups xmmword ptr [rsp + 40], xmm0 vmovups xmmword ptr [rsp + 88], xmm0 vmovups xmmword ptr [rsp + 72], xmm0 vmovups xmmword ptr [rsp + 120], xmm0 vmovups xmmword ptr [rsp + 104], xmm0 

.



 array<Point, bigSize> data; 

  sub rsp, 5144 

.


 array<ZeroPoint, bigSize> data; array<ZeroPoint, bigSize> data{}; array<Point, bigSize> data{}; 

  sub rsp, 5152 lea rbx, [rsp + 16] xor esi, esi mov edx, 5136 mov rdi, rbx call memset #  memset(rsp+16, 0, 5136). 

, memset . , , rdi, esi, edx . e d 32 64- .


 array<NanPoint, bigSize> data; 

  sub rsp, 5144 lea rax, [rsp + 8] lea rcx, [rsp + 5144] vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] #  . .LBB0_1: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax], xmm0 vmovups xmmword ptr [rax + 16], xmm0 vmovups xmmword ptr [rax + 32], xmm0 add rax, 48 cmp rax, rcx jne .LBB0_1 

. , 321 3 . rax, rcx .


 array<NanPoint, bigSize> data{}; 

  sub rsp, 5152 lea rbx, [rsp + 16] xor esi, esi mov edx, 5136 mov rdi, rbx call memset #  memset(rsp+16, 0, 5136). lea rax, [rsp + 5152] vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] #  . .LBB0_1: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rbx], xmm0 vmovups xmmword ptr [rbx + 16], xmm0 vmovups xmmword ptr [rbx + 32], xmm0 add rbx, 48 cmp rbx, rax jne .LBB0_1 

. , memset . , , . .



 vector<Point> data(smallSize); 

  sub rsp, 40 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 128 call operator new(unsigned long) #  new(128). mov qword ptr [rsp], rax mov rcx, rax sub rcx, -128 mov qword ptr [rsp + 16], rcx vxorps xmm0, xmm0, xmm0 #     . vmovups xmmword ptr [rax + 16], xmm0 vmovups xmmword ptr [rax], xmm0 vmovups xmmword ptr [rax + 32], xmm0 vmovups xmmword ptr [rax + 48], xmm0 vmovups xmmword ptr [rax + 64], xmm0 vmovups xmmword ptr [rax + 80], xmm0 vmovups xmmword ptr [rax + 96], xmm0 vmovups xmmword ptr [rax + 112], xmm0 

new . , T{} , , . ZeroPoint NanPoint . :


 vector<Point> data(bigSize); 

 main: # @main push rbx sub rsp, 48 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 5136 call operator new(unsigned long) #  new(5136). mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov rcx, rax add rcx, 5136 mov qword ptr [rsp + 16], rcx vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp + 32], xmm0 xor edx, edx #    . .LBB0_2: # =>This Inner Loop Header: Depth=1 vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rax + rdx], xmm0 vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rax + rdx + 16], xmm0 vmovaps xmm0, xmmword ptr [rsp + 32] vmovups xmmword ptr [rax + rdx + 32], xmm0 add rdx, 48 cmp rdx, 5136 jne .LBB0_2 mov qword ptr [rsp + 8], rcx mov rax, rsp 

, . NanPoint .


 vector<NanPoint> data(bigSize); 

 main: # @main push rbx sub rsp, 32 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 5136 call operator new(unsigned long) #  new(5136). mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov rcx, rax add rcx, 5136 mov qword ptr [rsp + 16], rcx xor edx, edx vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] #    NAN. .LBB0_2: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax + rdx], xmm0 vmovups xmmword ptr [rax + rdx + 16], xmm0 vmovups xmmword ptr [rax + rdx + 32], xmm0 add rdx, 48 cmp rdx, 5136 jne .LBB0_2 mov qword ptr [rsp + 8], rcx mov rax, rsp 

A ZeroPoint .


 vector<ZeroPoint> data(bigSize); 

  sub rsp, 32 vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 mov edi, 5136 call operator new(unsigned long) #  new(5136). mov qword ptr [rsp], rax mov rbx, rax add rbx, 5136 mov qword ptr [rsp + 16], rbx xor esi, esi mov edx, 5136 mov rdi, rax call memset #  memset(&data, 0, 5136). 

memset . , , memset Point . , , bigUnknownSize .


 vector<NanPoint> data(bigUnknownSize); 

  sub rsp, 32 mov rbx, qword ptr [rip + bigUnknownSize] vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 test rbx, rbx #  bigUnknownSize == 0,   new. je .LBB0_1 mov rax, rbx shr rax, 60 jne .LBB0_3 mov rdi, rbx shl rdi, 4 #   16    4. call operator new(unsigned long) #  new(bigUnknownSize*16). jmp .LBB0_6 .LBB0_1: xor eax, eax .LBB0_6: mov rcx, rbx shl rcx, 4 add rcx, rax mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov qword ptr [rsp + 16], rcx test rbx, rbx #  bigUnknownSize == 0,   . je .LBB0_14 lea rdx, [rbx - 1] mov rsi, rbx and rsi, 7 je .LBB0_10 neg rsi vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] #   0-7 . .LBB0_9: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax], xmm0 dec rbx add rax, 16 inc rsi jne .LBB0_9 .LBB0_10: cmp rdx, 7 jb .LBB0_13 vmovaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [nan,nan] #      8. .LBB0_12: # =>This Inner Loop Header: Depth=1 vmovups xmmword ptr [rax], xmm0 vmovups xmmword ptr [rax + 16], xmm0 vmovups xmmword ptr [rax + 32], xmm0 vmovups xmmword ptr [rax + 48], xmm0 vmovups xmmword ptr [rax + 64], xmm0 vmovups xmmword ptr [rax + 80], xmm0 vmovups xmmword ptr [rax + 96], xmm0 vmovups xmmword ptr [rax + 112], xmm0 sub rax, -128 add rbx, -8 jne .LBB0_12 .LBB0_13: mov rax, rcx .LBB0_14: mov qword ptr [rsp + 8], rax mov rax, rsp 

. , LBB0_9 , 8. 8 .


, Point , ZeroPoint memset :


 vector<ZeroPoint> data(bigUnknownSize); 

  sub rsp, 40 mov rbx, qword ptr [rip + bigUnknownSize] vxorps xmm0, xmm0, xmm0 vmovaps xmmword ptr [rsp], xmm0 mov qword ptr [rsp + 16], 0 test rbx, rbx #  bigUnknownSize == 0,   new. je .LBB0_1 mov rax, rbx shr rax, 60 jne .LBB0_3 mov rdi, rbx shl rdi, 4 call operator new(unsigned long) #  new(bigUnknownSize*16). jmp .LBB0_6 .LBB0_1: xor eax, eax .LBB0_6: mov rdx, rbx shl rdx, 4 lea r14, [rax + rdx] mov qword ptr [rsp], rax mov qword ptr [rsp + 8], rax mov qword ptr [rsp + 16], r14 test rbx, rbx #  bigUnknownSize == 0,   memset. je .LBB0_8 xor esi, esi mov rdi, rax call memset #  memset(&data, 0, bigUnknownSize*16). mov rax, r14 .LBB0_8: mov qword ptr [rsp + 8], rax mov rax, rsp 

,


 vector<NanPoint> data; data.resize(bigUnknownSize); 

250 , . operator new , .



.


CodeCycles per iteration
Point p;4.5
ZeroPoint p;5.2
NanPoint p;4.5
array<Point, smallSize> p;4.5
array<ZeroPoint, smallSize> p;6.7
array<NanPoint, smallSize> p;6.7
array<Point, bigSize> p;4.5
array<ZeroPoint, bigSize> p;296.0
array<NanPoint, bigSize> p;391.0
array<Point, bigSize> p{};292.0
array<NanPoint, bigSize> p{};657.0
vector<Point> p(smallSize);32.3
vector<ZeroPoint> p(smallSize);33.8
vector<NanPoint> p(smallSize);33.8
vector<Point> p(bigSize);323.0
vector<ZeroPoint> p(bigSize);308.0
vector<NanPoint> p(bigSize);281.0
vector<ZeroPoint> p(smallUnknownSize);44.1
vector<NanPoint> p(smallUnknownSize);37.6
vector<Point> p(bigUnknownSize);311.0
vector<ZeroPoint> p(bigUnknownSize);315.0
vector<NanPoint> p(bigUnknownSize);290.0
vector<NanPoint> p; p.resize(bigUnknownSize);315.0

:


  • .
  • .
  • memset , .
  • .
  • , . .
  • . .
  • , . .

Source: https://habr.com/ru/post/de414443/


All Articles