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 Basisadressea
Addend hält,rsi
Register Basisadresseb
Addend hält,rdx
Register hält Basisadresses
, sum ,eax
Registerkonten für beidec
, tragen, undt
, temporäre,r8
Register enthälti
, Schleifenzähler,ecx
Register enthältn
, 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
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
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
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