Seite 1 von 1

[C++] Anwendung für bestimmte CPU-Architektur optimieren

Verfasst: 12.09.2017, 14:21
von Goderion
Hallo.

In den letzten Monaten kamen ja so einige neue Prozessoren raus (Intel Skylake-X mit Mesh statt Ringbus, AMD Ryzen, usw.). Ich lese in einigen Foren immer wieder, das aktuelle Anwendungen in bestimmten Vergleichen mit den neuen Prozessoren ungewöhnlich schlecht abschneiden. In einigen Fällen läuft eine Anwendung schlechter (bis zu 30% langsamer), obwohl die CPU mehr Kerne und einen höheren Takt hat. Dies wird dann oft mit der neuen Architektur begründet und das dort die Entwickler das Programm noch für die CPU optimieren müssen.

Das Prozessoren mit unterschiedlicher Architektur nicht pro Takt die gleiche Leistung liefern, ist klar. Ein AMD Ryzen mit 3GHz wird anders performen als z.B ein Intel i7 mit 3Ghz. Mir geht es primär um die Aussage der Leute in den Foren, die impliziert, das z.B. ein Intel i7 7800X (Skylake-X mit Mesh) schneller ist als ein i7 7700K (Skylake mit Ringbus) bei gleichem Takt, aber es nur aufgrund mangelnder Optimierung öfter nicht in der Realität erreicht wird.

Ich habe absolut keine Ahnung, wie ich z.B. eine Anwendung für einen AMD Ryzen oder einen Intel Skylake-X mit Mesh optimieren kann. Google spuckt mir da spontan auch nix Nutzbares aus. Es geht hier auch anscheinend nicht um neue Prozessor-Befehle/Instructions oder bessere Parallelisierung, also wie soll man denn da dann was optimieren?

Re: [C++] Anwendung für bestimmte CPU-Architektur optimieren

Verfasst: 12.09.2017, 15:22
von Krishty
Goderion hat geschrieben:Ich habe absolut keine Ahnung, wie ich z.B. eine Anwendung für einen AMD Ryzen oder einen Intel Skylake-X mit Mesh optimieren kann. Google spuckt mir da spontan auch nix Nutzbares aus.
Intel stellt immer ein Optimization Manual zur Verfügung: https://www.intel.com/content/dam/www/p ... manual.pdf

Hier ist AMDs Richtlinie für Ryzen-Optimierung: http://support.amd.com/TechDocs/55723_S ... s_3.00.pdf

Ich kann dir nichts speziell zu Ryzen oder Skylake sagen, aber der Pentium 4 war wohl die bekannteste CPU mit speziellen Optimierungen. Beim Pentium 4 hatte man den CPU-Durchsatz drastisch erhöht (über 6 GHz ALU-Takt), musste dafür aber die Pipeline verlängern, was bedingte Sprünge langsamer machte (genauer: falsch vorhergesagte Sprünge). Viele Anwendungen wurden dadurch langsamer, aber optimierte Anwendungen wurden enorm schneller. Der Trick war, bedingte Sprünge durch Arithmetik zu ersetzen.

Als Intel 2011 Sandy Bridge einführte, nutzten sie erstmals einen µop-Cache. Dadurch wurden manuell abgerollte Schleifen langsamer (passten nicht mehr in den µop-Cache und bewirkten starke Decoding-Last) und nicht-abgerollte Schleifen schneller (konnten direkt aus dem Cache ausgeführt werden). Sowas spielt gerade bei Ryzen auch rein.

Agner Fog hat einen Artikel über Ryzen: http://www.agner.org/optimize/blog/read.php?i=838
Ein Knackpunkt ist wohl SMT:
If the CPU core has a higher capacity than a single thread can utilize then it makes sense to run two threads in the same core. The gain in total performance that you get from running two threads per core is much higher in the Ryzen than in Intel processors because of the higher throughput of the AMD core (except for 256-bit vector code).
Das klingt stark nach dem, was ich oben über Pentium 4 geschrieben habe: sie haben wahrscheinlich Latenz für Durchsatz geopfert, und das stinkt ab, sobald im Code viele Abhängigkeiten und Speicherzugriffe vorkommen. Wenn man kein SMT benutzt, dann umso mehr. Ist aber nur meine unqualifizierte 10-Minuten-googlen-Meinung.

Re: [C++] Anwendung für bestimmte CPU-Architektur optimieren

Verfasst: 12.09.2017, 21:15
von Goderion
Vielen Dank für die Antwort!

Ich programmiere seit ca. 20 Jahren, musste aber erstmal googlen, um herauszufinden, was Abrollen von Schleifen bedeutet. Darum liebe ich Programmierung, man lernt nie aus! ^^

Ich glaube aktuell verhält sich das mit dem Ringbus vs. Mesh ähnlich. Die Mesh-Architektur kann einen höheren Durchsatz erreichen, darunter leidet aber die Latenz. Programme, die wohl recht linear ablaufen, wie Kompression oder Encoding, laufen sehr gut im Mesh, aber einige andere Programme laufen mit Ringbus besser. Das ist aber auch mehr eine Vermutung, so richtig habe ich die Ringbus/Mesh-Architektur nicht verstanden. Die AMD Ryzen nutzen ein Infinity Fabric, welches ähnlich wie das Mesh von Intel arbeiten soll.

Das mit SMT/HT passt auch zu meinen Erfahrungen. Je mehr eine Anwendung parallel wild im Speicher "rumwuselt" und die Kerne oft auf Daten warten müssen, je mehr profitiert man von SMT/HT. Ich stell mir das immer vor wie ein Arbeiter, der zwei Tische vor sich stehen hat. Auf jedem Tisch wird irgendwas gemacht, aber der Arbeiter muss immer wieder mal auf andere warten, die was auf die Tische legen oder wegnehmen, damit er weitermachen kann. Er wechselt immer wieder von Tisch zu Tisch. In dieser Vorstellung profitiert der Arbeiter vom zweiten Tisch besonders, wenn er oft auf andere warten muss.

Ist es überhaupt möglich, eine Anwendung, die in ihren Funktionen viele "verteilte" Daten aus dem Speicher lesen und schreiben muss, dahingehend zu optimieren, dass sie von der Latenz nicht so "abhängig" ist? In meiner Anwendung gibt es Millionen von Objekten, die zwar pro Objektart teilweise linear im Speicher liegen, aber die Funktionen müssen in der Regel unterschiedliche Objektarten verarbeiten. Dadurch wird "wild" im Speicher gelesen und geschrieben. Das könnte man vermutlich nicht mehr als Optimierung bezeichnen, sondern eine komplette Umstrukturierung der Daten im Speicher. Man müsste wohl erreichen, dass Daten, die oft "zusammen" gelesen/geschrieben werden, dicht beieinander liegen.

Wie kann man denn bedingte Sprünge durch Arithmetik ersetzen?

Re: [C++] Anwendung für bestimmte CPU-Architektur optimieren

Verfasst: 12.09.2017, 21:27
von Krishty
Goderion hat geschrieben:Das mit SMT/HT passt auch zu meinen Erfahrungen. Je mehr eine Anwendung parallel wild im Speicher "rumwuselt" und die Kerne oft auf Daten warten müssen, je mehr profitiert man von SMT/HT.
Naja, der Nutzen ist sehr begrenzt, weil zwei Threads bedeuten, dass doppelt so oft auf den Speicher gewartet werden muss. Nützlich ist es eben vor allem bei 50:50 ALU-zu-Speicher-Befehlen. Mehr Rumwuseln, und der zweite Thread wartet ebenfalls auf den Speicher. Weniger Rumwuseln, und ALU ist wieder der Flaschenhals. Die Benchmarks haben sich wohl auch verschoben, weil ALU bei Ryzen so viel stärker geworden ist.
Goderion hat geschrieben:Ist es überhaupt möglich, eine Anwendung, die in ihren Funktionen viele "verteilte" Daten aus dem Speicher lesen und schreiben muss, dahingehend zu optimieren, dass sie von der Latenz nicht so "abhängig" ist? In meiner Anwendung gibt es Millionen von Objekten, die zwar pro Objektart teilweise linear im Speicher liegen, aber die Funktionen müssen in der Regel unterschiedliche Objektarten verarbeiten. Dadurch wird "wild" im Speicher gelesen und geschrieben. Das könnte man vermutlich nicht mehr als Optimierung bezeichnen, sondern eine komplette Umstrukturierung der Daten im Speicher. Man müsste wohl erreichen, dass Daten, die oft "zusammen" gelesen/geschrieben werden, dicht beieinander liegen.
Das ist die wichtigste Optimierung und hat normalerweise die größte Auswirkung. Davon ab kannst du höchstens versuchen, mit Prefetching was zu reißen.
Goderion hat geschrieben:Wie kann man denn bedingte Sprünge durch Arithmetik ersetzen?
abs(int) mit Sprung:

Code: Alles auswählen

int abs(int i) {
  if(i < 0) {
    return -i;
  } else {
    return i;
  }
}
Ohne Sprung (via https://graphics.stanford.edu/~seander/ ... IntegerAbs):

Code: Alles auswählen

int abs(int i) {
  int const mask = i >> sizeof(int) * CHAR_BIT - 1;
  return (i ^ mask) - mask;
}
Insbesondere mit SIMD läuft es darauf hinaus, dass man 1) eine Bitmaske anlegt, 2) beide Verzweigungen durchrechnet, 3) das Ergebnis durch die Bitmaske auswählt. Oder durch cmov.

Re: [C++] Anwendung für bestimmte CPU-Architektur optimieren

Verfasst: 13.09.2017, 11:05
von Schrompf
cmov ist wohl der primäre Ansatzpunkt. Oder die entsprechenden Instruktionen der SIMD-Erweiterungen, die das selbe element-weise auf Vektoren tun. Woher jetzt das spezifische Performance-Verhalten auf den neuen Inter-Core-Kommunikationsstrukturen herkommen soll, weiß ich allerdings auch nicht. Wenn ich aber mal wieder Zeit habe, werde ich meinen Voxel-Scheiß mal hardcore vektorisieren müssen. Die Bitschubsereien auf 8xByte in einem uint64_t hatten schon ordentlich was bewegt.

Re: [C++] Anwendung für bestimmte CPU-Architektur optimieren

Verfasst: 13.09.2017, 12:42
von Krishty
Schrompf hat geschrieben:Woher jetzt das spezifische Performance-Verhalten auf den neuen Inter-Core-Kommunikationsstrukturen herkommen soll, weiß ich allerdings auch nicht.
Ganz ins Blaue geschossen:

Das ist enorm wichtig, wenn verschiedene Kerne auf den selben Variablen arbeiten. Ich habe z.B. oft ein bool abort für alle Threds, mit dem ich meine Arbeitspakete abbrechen kann; oder ein int remainingItems, mit dem sich Threads die nächsten Arbeitspakete holen.

Wird eine dieser Variablen geändert, müssen alle anderen Kerne
  1. die Cache-Zeile wegschmeißen, falls die Variable bei ihnen im Cache lag
  2. spekulativ ausgeführte Anweisungen, die auf dieser Variable aufbauen, wegschmeißen
  3. die Cache-Zeile vom Kern, der sie verändert hat, neu anfordern
  4. spekulative Berechnungen, die auf dieser Variable aufbauen, neu beginnen
Man sieht, dass das komplex ist. Es ist noch komplexer, zu entscheiden, welcher Thread bei SMT/HT weiterlaufen darf, während ein Kern seinen Cache aktualisiert. (In der grauen Vorzeit hat man bei sowas einfach alle betroffenen CPU-Kerne angehalten.)

Wie schnell das alles geht, hängt sehr stark von der Bus-Architektur und dem Protokoll der Kommunikation zwischen den Kernen ab. Gut möglich, dass sich die Leistung da radikal ändert, wenn man einen anderen Bus ausliefert.

Intel hatte mal Problem mit ihrem neuen Synchronisationsmechanismus. Sie haben ja spekulative Ausführung und Cache-Validität direkt dem Programmierer zur Verfügung gestellt, um Synchronisierungsvariablen bestenfalls komplett zu vermeiden. Die erste Generation war aber viel langsamer als klassische Synchronisierung (und dann hatten sie noch einen blockierenden Fehler drin).

Nachtrag: Ryzen gruppiert die Kerne in CPU Complexes, und offensichtlich ist die Kommunikation zwischen zwei Kernen sehr schnell, wenn sie im selben CPU Complex liegen entfällt innerhalb eines CCX die Kommunikation, weil sich beide Kerne den selben Cache teilen; ist aber sehr langsam zwischen getrennten CCX. Dafür Skaliert die Architektur zu mehr Kernen. Ich rate also, dass die Lösung ist, nicht alle Threads global zu synchronisieren, sondern hierarchisch gemäß den CPU-Kernen, auf denen sie ausgeführt werden.

Ich stelle mir da als erstes die Frage: Warum sollte sich das durchsetzen? Tiefes Pipelining, Alignment, asynchrone Caches, … das wurde alles abgeschafft weil niemand Bock drauf hatte und Intel-CPUs ohne konnten.
There is a significant reduction in performance, when the game thread and driver thread are on different CCX units. With 3200Mhz DDR4, the draw call performance is exactly like that of a Core 2 Duo.
Ach du heilige Scheiße!