Zurück zur Startseite

Spekulative Ausführung vs verzweigungsfreier Code

Der Artikel analysiert den Einfluss von verzweigungsfreiem Code auf die Performance unter Berücksichtigung von spekulativer Ausführung und Cache-Hierarchie. Beispiele für Schleifen, Suche und Binärsuche mit Code werden bereitgestellt. Empfehlungen zur Auswahl eines Ansatzes je nach Datenspeicherort.

Verzweigungsfreier Code vs Spekulation: Wer ist schneller auf L3 und DRAM?
Advertisement 728x90

Spekulative Ausführung und verzweigungsfreier Code: Versteckte Leistungskompromisse

Moderne Prozessoren führen Milliarden von Operationen pro Sekunde aus, aber die Speicherzugriffsgeschwindigkeiten wachsen langsamer. Diese Lücke bedeutet, dass das Warten auf Daten zu einer Hauptursache für Leistungsverluste wird. Prozessoren haben sich von passiven Ausführern zu aktiven Datenfluss-Managern entwickelt: Out-of-Order-Ausführung, Abhängigkeits-Neuordnung und spekulative Ausführung.

Die Sprungvorhersage ermöglicht es, Ladevorgänge frühzeitig zu starten und so die Speicherlatenz zu verbergen. Eine Fehlvorhersage kostet 10–20 Zyklen, aber das Warten auf L3 oder DRAM dauert Hunderte. Verzweigungsfreier Code eliminiert Fehlvorhersagen, nimmt dem Prozessor aber die Fähigkeit zur Spekulation.

Entwicklung von sequenzieller zu spekulativer Ausführung

Frühe Systeme führten Befehle streng sequenziell aus. Der IBM System/360 Model 91 führte den Tomasulo-Algorithmus für Out-of-Order-Ausführung ein: Befehle starten, wenn Operanden bereit sind.

Google AdInline article slot

Spekulative Ausführung entstand später in superskalaren Prozessoren wie dem Intel Pentium Pro (1995). Eine 40-Micro-Op-Pipeline hält Dutzende von Befehlen in der Ausführung, wobei Sprünge basierend auf der Historie vorhergesagt werden.

for (size_t i = 0; i < n; i++) {
    if (arr[i] >= 0) {
          ... code_true ...;
    } else {
        ... code_false ...;
    }
}

Hier kann das Laden von arr[i] zu einer Verzögerung von 10–100+ Zyklen führen, wenn die Daten nicht in L1 sind. Der Prozessor sagt den Sprung voraus, startet code_true und initiiert Prefetch. Bei einer Fehlvorhersage – Flush und Neustart.

Verzweigungsfrei vs. verzweigungsbehaftet: Cache-Abhängigkeit

Vereinfacht mit Verzweigung:

Google AdInline article slot
if (arr[i] > 0) {
    result++;
}

Wird zu:

result += (arr[i] > 0);
  • Daten in L1: verzweigungsfrei gewinnt – keine Fehlvorhersage, minimale Verzögerung.
  • L2: neutral, die Kosten der Fehlvorhersage sind gering.
  • L3/DRAM: verzweigungsbehaftet ist schneller – Spekulation löst Prefetch frühzeitig aus und verdeckt Latenz.

Verzweigungsfrei verschiebt die Verzögerung in den kritischen Pfad: Der Prozessor wartet auf Daten vor dem Vergleich, ohne Berechnungen zu überlappen.

Wertsuche: Die Kosten bedingungsloser Ladevorgänge

Original:

Google AdInline article slot
Value result = default_value;
for (size_t i = 0; i < n; ++i) {
    if (keys[i] == target) {
        result = values[i];
    }
}
return result;

Verzweigungsfrei:

Value result = default_value;
for (size_t i = 0; i < n; ++i) {
    result = (keys[i] == target) ? values[i] : result;
}
return result;

Bei verzweigungsfreiem Code lädt values[i] bei jeder Iteration, was Bandbreite für das gesamte Array verschwendet. Das Original liest nur bis zu einer Übereinstimmung.

Binäre Suche: Abhängigkeitsketten

Verzweigungsbehaftet:

while (low < high) {
    size_t mid = low + (high - low) / 2;
    if (keys[mid] < target)
        low = mid + 1;
    else
        high = mid;
}

Der Prozessor berechnet spekulativ das nächste mid und prefetched keys[mid].

Verzweigungsfrei:

while (low < high) {
    size_t mid = low + (high - low) / 2;
    size_t step = (high - low) / 2;
    low  = (keys[mid] < target) ? mid + 1 : low;
    high = (keys[mid] < target) ? high    : mid;
}

Abhängigkeiten vom Vergleichsergebnis blockieren Spekulation: Das nächste mid wartet auf low/high-Schreibvorgänge.

  • Die Hälfte der Vorhersagen ist falsch, aber frühes Prefetch überlappt den Flush.
  • Verzweigungsfrei: mehr Befehle, spätere Ladevorgänge, explizite Speicherverzögerungen.

Wichtige Erkenntnisse

  • Spekulation verdeckt Speicherlatenz durch Auslösen von Prefetch bei vorhergesagten Sprüngen.
  • Verzweigungsfrei eliminiert Fehlvorhersagen, verschlechtert aber die Leistung bei L3/DRAM-Zugriff.
  • Profiling auf Zielhardware: L1 – verzweigungsfrei, große Arrays – verzweigungsbehaftet.
  • Konsolenspiel-Portierungen leiden oft unter der Vernachlässigung dieser Effekte.
  • Abhängigkeiten in verzweigungsfreien Ketten blockieren OoO-Optimierungen.

Mit zufälligen Daten in L1 dominiert verzweigungsfreier Code. Mit Mustern oder großen Volumen gewinnt verzweigungsbehafteter Code aufgrund spekulativer Ladevorgänge.

— Editorial Team

Advertisement 728x90

Weiterlesen