2016-06-14 23 views
2

Ich wundere mich, meistens aus Neugierde, wenn das gleiche Register für eine Operation besser ist als zwei. Was wäre besser, wenn man Leistung und/oder andere Bedenken berücksichtigt?In x86-Assembly ist es besser, zwei separate Register für imul zu verwenden?

mov %rbx, %rcx 
imul %rcx, %rcx 

oder

mov %rbx, %rcx 
imul %rbx, %rcx 

Irgendwelche Tipps, wie zum Benchmark dieses oder Ressourcen, wo ich über diese Art der Sache würde geschätzt lesen konnte, wie ich Montage neu bin.

Antwort

4

Ressourcen, wo ich über diese Art der Sache

Siehe Agner Fog's microarch pdf und seine Optimierungsmontageanleitung lesen konnte. Auch andere Links im 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)).

5

Bei einem modernen Prozessor spielt die Verwendung eines Registers für Quelle und Ziel und die Verwendung von zwei verschiedenen Registern für die Leistung keine Rolle. Der Grund dafür liegt teilweise an register renaming, die, wenn es einen Unterschied in der Leistung gäbe, es lösen würde, indem Sie eines der Register zu einem anderen ändern und Ihre nachfolgenden Anweisungen ändern, um das neue Register zu verwenden (Ihr Prozessor hat tatsächlich mehr Register als das Befehlssatz hat eine Art, sich auf sie zu beziehen, so dass sie solche Dinge tun kann). Es liegt auch an der Art der Implementierung eines Pipeline-Prozessors - der Inhalt von Quellregistern wird in einer Pipeline-Stufe gelesen und dann in einer anderen späteren Phase geschrieben, was es schwierig oder unmöglich macht, Register für eine einzelne Anweisung zu verwenden Art der Interaktion wie die, um die du dich sorgst.

Problematischer ist es, wenn sich eine Anweisung auf einen Wert bezieht, der in ihrer vorherigen Anweisung erzeugt wurde, aber selbst das wird (normalerweise) durch out-of-order execution gelöst.

+0

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). –

+1

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. –