2016-05-21 8 views
1

Hintergrund: GCC 6.1 bei -O3 Optimierungsstufe generiert diese assembly:Excess Zwischenspeicheranweisung in ganzzahligen zusätzlich

test ecx, ecx 
    je  .L8 
    xor  r8d, r8d 
    xor  eax, eax 

.L7: 
    xor  r9d, r9d 
    add  eax, DWORD PTR [rdi+r8*4]  ; adding a[i] 
    setc r9b 
    add  eax, DWORD PTR [rsi+r8*4]  ; adding b[i] 
    mov  r11d, eax      ; excessive mov (#1) 
    setc r10b 
    mov  DWORD PTR [rdx+r8*4], r11d ; storing at s[i] 
    add  r8, 1 
    movzx r10d, r10b 
    cmp  ecx, r8d 
    lea  eax, [r10+r9]     ; sorcery (#2) 
    ja  .L7 
    rep ret 

.L8: 
    xor  eax, eax 
    ret 

für diese Funktion:

limb_t add(
    const limb_t *a, 
    const limb_t *b, 
    limb_t *s, 
    int n 
) { 
    limb_t c = 0, t = 0; 

    for (int i = 0; i < n; ++i) { 
     t = a[i] + c; 
     c = t < a[i]; 

     t += b[i]; 
     c += t < b[i]; 

     s[i] = t; 
    } 

    return c; 
} 

wo a, b und s ganze Zahlen von gleiche Länge n Gliedmaßen, gespeichert im Speicher als eine kontinuierliche Sequenz von 32-Bit-Einheiten (Gliedmaßen, Ziffern) in Little Endian (das heißt, der erste ist t er am wenigsten signifikante Gliedmaße).

Diese Funktion fügt zwei nicht negative Summa a und b, speichert die Summe in s und gibt den Übertrag c. Die temporäre Variable t enthält den aktuellen Summenzweig und aktiviert die Szenarien a == s und b == s.

Wie ich von der Anordnung abgeleitet,

  • rdi Register Basisadresse a Addend hält,
  • rsi Register Basisadresse b Addend hält,
  • rdx Register hält Basisadresse s, sum ,
  • eax Registerkonten für beide c, tragen, und t, temporäre,
  • r8 Register enthält i, Schleifenzähler,
  • ecx Register enthält n, Länge in Gliedern von Addenden und sum.

Meine erste Frage ist:

1. Warum ist die Zwischenlagerung von eax Registerwert in r11d Registern stattfinden, bevor es in dem Speicher [rdx + r8*4] (aktuelle Schenkel Summe) zu verschieben?

Ich sehe keine andere Verwendung von r11 Register, aber für diese übermäßige Speicheroperation; und mov Anweisung tatsächlich erlaubt bewegen von eax registrieren, warum nicht den Wert von dort verschieben?


Meine zweite Frage lautet:

2. Was ist das für Zauberei mit lea Anweisung und tragen Werte?

lea  eax, [r10+r9]     ; sorcery (#2) 

Was berechnet es eigentlich? lea = r10 + r9? Und in diesem Fall, warum müssen wir hohe Bits von r10 jede Schleife Iteration mit dieser movzx Anweisung löschen?

movzx r10d, r10b 
+0

Haben Sie einen Benchmark-Vergleich mit dem Code durchgeführt, der Ihrer Meinung nach besser ist? Was war das Ergebnis? Warum kümmerst du dich? Hast du das Befehlstiming genau dieser Sequenz im Vergleich zu deinem "besseren" Code überprüft (einschließlich Registerumbenennung, Pipeline-Blockierungen, Setc.)? – Olaf

+0

Nun, es ist wirklich eine Herausforderung, solchen High-Level-C-Code zu schreiben, der effizient genug Assembly kompilieren würde. Ich dachte, vielleicht war es etwas, das ich im Quellcode gemacht habe, was zu dieser suboptimalen Assemblierung führt. Ziemlich einfache Optimierungen der * generierten * Assemblierung könnten die Anzahl der Instruktionen pro Schleifeniteration von aktuell 13 auf 8 verringern, indem überschüssige Register außerhalb des Zyklus definiert werden, wobei 'setc' und' add' durch 'adc' ersetzt werden (wie von @Jester vorgeschlagen) Entfernen des Zwischenspeichers "r11". – aprelev

+0

Die einzige Möglichkeit, es zu testen, besteht darin, es als Inline-Assembly zu codieren und gegen die von GCC generierte Version zu laufen, und ich bin noch nicht so weit gegangen. Die Frage war, warum der Compiler solche Dinge tun sollte, vielleicht könnte generierter Code durch einige rationale Gründe unterstützt werden, von denen ich keine Ahnung habe. Ich denke, der beste Weg, den effizientesten Code zu schreiben, besteht darin, die Assembly, die von GMP oder OpenSSL für diese Art von Dingen zur Verfügung gestellt wird, gründlich zu untersuchen. – aprelev

Antwort

2

Der erste scheint zu "verpasste Optimierung" Compiler Bug zu sein.

Die zweiten Bedürfnisse c der Größe dword für die t = a[i] + c; zu erzeugen, und es tut, die von den zwei logischen Werten erstrecken, mit unterschiedlichen Methoden, die zwar etwas seltsam ist:

t < a[i]; durch xor r9d, r9d durchgeführt wird und setc r9b aber t < b[i] wird von dem setc r10b und movzx r10d, r10b Paar durchgeführt. Es ist nicht sofort offensichtlich, aber dies könnte legitime Gründe für die Instruktionsplanung haben.

Das Hinzufügen der zwei logischen Werte erfolgt durch die lea eax, [r10+r9], die aus zwei Gründen statt add verwendet wird. Erstens betrifft es keine Flags, so dass es zwischen dem cmp und dem ja eingefügt werden kann. Zweitens kann es eine Ausgabe in einem dritten Register erzeugen.

Eine andere Möglichkeit wäre, die zwei logischen Werte zuerst hinzuzufügen und nur das Ergebnis zu erweitern. Nicht sicher, ob das ein besserer Ansatz wäre. Außerdem würde ein temporäres Register ausreichen.

+0

Sicher, 'xor r9d, r9d' und' movzx r10d, r10b' könnten vor dem Zyklus ausgeführt werden? Gibt es einen bestimmten Grund für die Ausführung jeder Iteration? Und könnten Sie bitte * ein temporäres Register * ausarbeiten? – aprelev

+1

'Xor' könnte sein, aber das ist eine Abhängigkeit, die Idiom bricht, könnte also schlimmer sein. Das 'movzx' kann nicht, da das' r10b' benötigt, das in der Schleife gesetzt wird. Der Code verwendet 'r9' und' r10' für die zwei logischen Werte und setzt ihre Summe in 'eax'. Könnte mit dem zweiten Wert in 'eax' gemacht werden, also mit einem weniger Register. Auch das ist vielleicht nicht schneller.x86 Optimierung ist kompliziert :) – Jester

+0

Nein, was ich sage ist, dass es scheint, dass es möglich ist, die Werte von 'r9' und' r10' im Voraus zu löschen, da nur das niedrigstwertige Bit sich in der Iteration ändern kann, und * * Es ist nicht notwendig, die hohen Bits dieser Register innerhalb von Iterationen über 'x oder X, X' oder' movxz Xd, Xb' ** explizit zu löschen. – aprelev