Ressourcen, wo ich über diese Art der Sache
Siehe Agner Fog's microarch pdf und seine Optimierungsmontageanleitung lesen konnte. Auch andere Links im x86 Tag-Wiki (z. B. Intels Optimierungshandbuch).
Die interessante Option, die Sie nicht erwähnt ist:
mov %rbx, %rcx
imul %rbx, %rbx # doesn'y have to wait for mov to execute
# old value of %rbx is still available in %rcx
Wenn die imul
ist auf dem kritischen Pfad und mov
hat nicht Null Latenz (wie auf AMD CPUs und Intel vor Ivybridge), das ist möglicherweise besser. Das Ergebnis von imul
ist einen Zyklus früher bereit, weil es keine Abhängigkeit vom Ergebnis der mov
gibt.
Wenn jedoch der alte Wert auf dem kritischen Pfad liegt und der quadratische Wert nicht, dann ist dies schlechter, da der kritische Pfad mov
hinzugefügt wird.
Natürlich bedeutet es auch, dass Sie die Tatsache im Auge behalten müssen, dass Ihre alte Variable jetzt in einem anderen Register lebt und das alte Register den quadrierten Wert hat. Wenn dies ein Problem in einer Schleife ist, entrollen Sie es, so dass Sie mit Dingen enden können, bei denen der obere Teil der Schleife sie erwartet. Wenn Sie möchten, dass dies einfach ist, würden Sie einen Compiler verwenden, anstatt asm von Hand zu optimieren.
jedoch Intel P6-Familie CPUs (PPro/PII zu Nehalem) haben begrenzte Register-Lese-Ports, so kann es besser sein, das Lesen Register zu bevorzugen, die Sie gerade geschrieben haben.Wenn %rbx
nicht in den letzten paar Zyklen geschrieben wurde, muss es aus der permanenten Registerdatei gelesen werden, wenn die mov
und imul
uops durchlaufen die Umbenennung & Ausgabestufe (die RAT).
Wenn sie nicht als Teil der gleichen Gruppe von 4 ausgeben, dann müssten sie jeweils separat %rbx
lesen. Da die Registerdatei in Core2/Nehalem nur über drei Leseports verfügt, werden die Ausgabegruppen (Quartetts, wie Agner Fog sie nennt) angehalten, bis alle ihre nicht zuletzt geschriebenen Eingangsregisterwerte aus der Registerdatei gelesen wurden (bei 3 pro Zyklus oder 2 auf Core2 ist keiner der 3 Regs Indexregs in einem Adressierungsmodus).
Für die vollständigen Details, siehe Agner Fog's microarch pdf Abschnitt 8.8. Der Core2-Abschnitt verweist auf den PPro-Abschnitt. PPro hat eine 3-weite Pipeline, und in diesem Abschnitt spricht Agner von Drillingen, nicht von Quartetten.
Wenn mov
und imul
Problem zusammen, sie beide die gleiche Lese von %rbx
teilen. Es gibt eine 3 in 4 Chance, dass dies auf Core2/Nehalem passiert.
Wenn Sie nur zwischen den Sequenzen wählen, die Sie erwähnen, hat der erste einen klaren (aber normalerweise kleinen) Vorteil gegenüber dem zweiten für CPUs der Intel P6-Familie. Es gibt keinen Unterschied für andere CPUs, AFAIK, also ist die Wahl offensichtlich.
mov %rbx, %rcx
imul %rcx, %rcx # uses only the recently-written rcx; can't contribute to register-read stalls
schlimmsten aus beiden Welten:
mov %rbx, %rcx
imul %rbx, %rcx # can't execute until after the mov, but still reads a potentially-old register
Wenn Sie auf einem kürzlich geschriebenen Register abhängen gehen, könnten Sie auch verwenden nur kürzlich geschriebenen Register.
Intel Sandybridge-Familie verwendet eine physikalische Registerdatei (wie AMD Bulldozer-Familie), und muss nicht registrieren ablesbare Ständen.
Ivybridge (2. Generation Sandybridge) und später auch mov reg,reg
bei Register umbenennen Zeit, mit Null Latenz und keine Ausführungseinheit. Dies bedeutet, dass es egal ist, ob Sie imul rbx
oder rcx
bis zur kritischen Pfadlänge.
Allerdings kann AMD Bulldozer-Familie nur Xmm-Registerbewegungen in seiner Umbenennungsstufe handhaben; Integer-Registerbewegungen haben immer noch 1c Latenz.
Es ist möglicherweise immer noch wichtig, sich darum zu kümmern, welche Abhängigkeitskette der mov
ist, wenn Latenz ein begrenzender Faktor in den Zyklen pro Iteration einer Schleife ist.
wie Benchmark diese
Ich glaube, Sie könnten ein-Micro zusammen, die ein Register lesen Stall auf Core2 mit imul %rbx, %rcx
hat, aber nicht mit imul %rcx, %rcx
. Dies würde jedoch einige Versuche und Fehler erfordern, um die mov
und imul
in verschiedenen Gruppen ausgeben zu lassen, und es sei denn, Sie fühlen sich wirklich kreativ, wahrscheinlich ein künstlich aussehender Umgebungscode, der nur existiert, um viele Register zu lesen. (z. B. lea (%rsi, %rdi, 1), %eax
oder sogar add (%rsi, %rdi, 1), %eax
(das alle drei Register lesen muss und eine Mikrosicherung auf core2/nehalem ausführt, so dass es nur 1 uop-Steckplatz in einer Problemgruppe benötigt.(Es doesn't micro-fuse on SnB-family)).
Dies ist eine sehr vage Art zu sagen "Ich glaube nicht" und verlinken zu einigen nützlichen Wiki-Artikeln. Das Umbenennen von Registern wird nicht einmal berücksichtigt: Auch ohne Umbenennung findet das Lesen von Registern vor dem Zurückschreiben statt. Diese vorgeschlagene Code-Sequenz hat nicht mehrere dep-Ketten, die dasselbe architektonische Register wiederverwenden (was ist, wenn Registerumbenennung ihre Magie ausübt und die Abhängigkeit bricht). –
Wie auch immer, ich werde dies ablehnen müssen, weil es eigentlich in beiden Punkten falsch ist: 1. Intel P6-Familien-CPUs wie Nehalem sind immer noch weit verbreitet und haben begrenzte Register-Lese-Ports. 2. Die Länge von Loop-getragenen Abhängigkeitsketten spielt immer noch eine Rolle mit der Out-of-Order-Ausführung. Beide OPs-Sequenzen haben die gleiche Latenz, aber eine Alternative ist möglich. Wenn Sie die Latenz reduzieren können, ohne dass es noch schlimmer wird, sollten Sie es tun. Wenn Sie asm von Hand schreiben (oder die Compilerausgabe lesen oder einen Compiler schreiben), dann ist alles potentiell wichtig. –