Gibt es ein wirklich funktionierendes Beispiel, das die Vorteile von ILP (Instruction-Level Parallelism) auf x86_64 zeigt?

8

Wie bekannt CPU ist Pipeline, und es funktioniert am effizientesten, wenn die Reihenfolge der Befehle unabhängig voneinander - dies als ILP (Instruction-Level Parallelism) bekannt: Ссылка

Aber gibt es ein wirklich funktionierendes Beispiel, das die Vorteile von ILP zeigt, zumindest synthetisches Beispiel für CPU x86_64 (aber für die gleiche Menge an cmp / jne in beiden Fällen ) ?

Ich werde das folgende Beispiel schreiben - addiere alle Elemente des Arrays, aber es zeigt keine Vorteile von ILP: Ссылка

  • Sequenziell:
%Vor%
  • ILP:
%Vor%

Ergebnis:

  

seq: 0,100000 s, Res: 1000000000, IPL: 0,110000 sec, schneller 0,909091   X, Res: 1000000000

     

seq: 0.100000 sec, Res: 1000000000, IPL: 0.100.000 sec, schneller 1.000000   X, Res: 1000000000

     

seq: 0,100000 s, Res: 1000000000, IPL: 0,110000 sec, schneller 0,909091   X, Res: 1000000000

     

seq: 0.100000 sec, Res: 1000000000, IPL: 0.100.000 sec, schneller 1.000000   X, Res: 1000000000

     

seq: 0,110000 Sekunden, Res: 1000000000, IPL: 0,110000 Sekunden, schneller 1,000000   X, Res: 1000000000

     

seq: 0,100000 s, Res: 1000000000, IPL: 0,110000 sec, schneller 0,909091   X, Res: 1000000000

     

seq: 0,100000 s, Res: 1000000000, IPL: 0,110000 sec, schneller 0,909091   X, Res: 1000000000

     

seq: 0,110000 Sekunden, Res: 1000000000, IPL: 0,100000 Sekunden, schneller 1.100000   X, Res: 1000000000

     

seq: 0,110000 Sekunden, Res: 1000000000, IPL: 0,100000 Sekunden, schneller 1.100000   X, Res: 1000000000

     

seq: 0,110000 Sekunden, Res: 1000000000, IPL: 0,120000 Sekunden, schneller 0,916667   X, Res: 1000000000

     

schneller AVG: 0.975303

ILP sogar ein bisschen langsamer als Sequential.

C-Code: Ссылка

%Vor%

UPDATE:

  • Sequenziell (Disassembler MS Visual Studio 2013) :
%Vor%
  • ILP (Disassembler MS Visual Studio 2013) :
%Vor%

Compiler-Befehlszeile:

%Vor%

Zusätzliche Hinweise zur Antwort:

Das einfache Beispiel, das die Vorteile von ILP zwischen Unroll-loop und Unroll-loop + ILP für ein Array von 50000000 Doppelelementen zeigt: Ссылка

  

schneller AVG: 1.152778

  • False-Sequential , das durch CPU-Pipeline (Disassembler MS Visual Studio 2013) optimiert werden kann - für das Hinzufügen von 8 Elementen in jeder Iteration wird das temporäre Register xmm0 verwendet, das dann zum Ergebnis xmm6 beiträgt kann verwendet werden Umbenennen registrieren :
%Vor%
  • True-Sequential , das nicht durch CPU-Pipeline (Disassembler MS Visual Studio 2013) optimiert werden kann - für 8 Elemente in jeder Iteration wird das Ergebnisregister xmm6 verwendet, dh es kann nicht Registrierung umbenennen :
%Vor%     
Alex 02.01.2015, 20:16
quelle

2 Antworten

15

Bei den meisten Intel-Prozessoren dauert es drei Zyklen, um eine Gleitkomma-Addition durchzuführen. Aber es kann bis zu 1 / Zyklus aufrechterhalten, wenn sie unabhängig sind.

Wir können ILP leicht demonstrieren, indem wir einen Gleitkomma-Add auf den kritischen Pfad setzen.

Umgebung:

  • GCC 4.8.2: -O2
  • Sandy Bridge Xeon

Stellen Sie sicher, dass der Compiler keine unsicheren Fließkomma-Optimierungen durchführt.

%Vor%

Ausgabe:

%Vor%

Beachten Sie, dass der Unterschied fast genau 3x ist. Das liegt an der 3-Zyklen-Latenz und dem 1-Zyklus-Durchsatz der Gleitkomma-Addition.

Die sequentielle Version hat sehr wenig ILP, weil alle Gleitkomma-Additionen auf dem kritischen Pfad sind. (Jede Erweiterung muss warten, bis die vorherige Erweiterung abgeschlossen ist.) Die nicht gerollte Version verfügt über 4 separate Abhängigkeitsketten mit bis zu 4 unabhängigen Adds, die alle parallel ausgeführt werden können. Nur 3 werden benötigt, um den Prozessorkern zu sättigen.

    
Mysticial 02.01.2015, 20:47
quelle
4

Der Unterschied kann auch für ganzzahligen Code sichtbar gemacht werden, zum Beispiel

%Vor%

Die Ergebnisse auf meiner 4770K sind etwa 35,9 Millionen TSC-Ticks für die erste vs 11,9 Millionen für die zweite (Mindestzeit über 1k Läufe).

In der ersten ist die Abhängigkeitskette in eax die langsamste Sache bei 4 Zyklen pro Iteration. Nichts anderes ist von Bedeutung, die Abhängigkeitskette von ecx ist schneller und es gibt viel Durchsatz, um sie und den Kontrollfluss zu verbergen. 35,9 Millionen TSC-Ticks funktionieren übrigens auf 40 Millionen Zyklen, da der TSC bei der Basistaktrate von 3,5 GHz anschlägt, aber der maximale Turbo bei 3,9 GHz liegt, 3,9 / 3,5 · 35,9 bei etwa 40.

Die Version des zweiten, die ich in den Kommentaren erwähnt habe (4 Akkumulatoren, aber mit [rsp] , um die Konstante 1 zu speichern), nimmt 17,9, was es 2 Zyklen pro Iteration macht. Das entspricht dem Durchsatz der Speicherlasten, der bei Haswell 2 / Zyklus ist. 4 lädt so 2 Zyklen. Der Schleifenoverhead kann sich immer noch verstecken.

Der zweite wie oben beschrieben benötigt 1.3333 Zyklen pro Iteration. Die ersten vier Adds können zu den Ports 0, 1, 5 und 6 gehen, das add/jnz fusionierte Paar kann nur zu Port 6 gehen. Wenn man das fusionierte Paar in p6 setzt, bleiben 3 Ports für 4 μops übrig, also 1.3333 Zyklen.

    
harold 02.01.2015 21:12
quelle