Einleitung: Das Paradoxon, das Stack Overflow sprengte
\n\nStellen Sie sich vor: Sie schreiben eine einfache Schleife in Java, die die Elemente eines Arrays in Abhängigkeit von einer Bedingung summiert (z. B. wenn die Zahl größer als 128 ist). Sie führen den Code auf einem sortierten Array aus – und er wird 2- bis 3-mal schneller ausgeführt als auf einem unsortierten. Klingt nach Magie? Tatsächlich ist es keine Magie, sondern Branch Prediction (Sprungvorhersage) – eine der Schlüsseleigenschaften moderner Prozessoren.
\n\nDie Frage „Why is conditional processing of a sorted array faster than of an unsorted array?“ erhielt auf Stack Overflow über 30.000 Stimmen und wurde zum Klassiker für das Studium der Low-Level-Optimierung. In diesem Artikel analysieren wir nicht nur die Theorie, sondern schreiben auch echten Java-Code, um den Effekt selbst zu sehen. Und vor allem erfahren Sie, wie Sie dieses Wissen in der Praxis anwenden können.
\n\nWenn Sie tiefer verstehen möchten, wie Java-Code-Optimierung funktioniert und warum Branch Prediction Ihr Programm sowohl beschleunigen als auch verlangsamen kann – sind Sie hier genau richtig.
\n\n1. Experiment: Der Code, der alles zeigt
\n\nBeginnen wir mit einer einfachen Java-Anwendung. Wir erstellen zwei Arrays: eines sortiert, das andere zufällig. Dann durchlaufen wir jedes und zählen die Summe der Elemente, die größer als ein Schwellenwert (z. B. 128) sind. Wir messen die Ausführungszeit.
\n\nimport java.util.Arrays;\nimport java.util.Random;\n\npublic class BranchPredictionDemo {\n private static final int SIZE = 100_000;\n private static final int THRESHOLD = 128;\n\n public static void main(String[] args) {\n // Erstellen eines unsortierten Arrays\n int[] unsorted = new int[SIZE];\n Random rand = new Random();\n for (int i = 0; i < SIZE; i++) {\n unsorted[i] = rand.nextInt(256); // 0..255\n }\n\n // Erstellen eines sortierten Arrays (kopieren und sortieren)\n int[] sorted = unsorted.clone();\n Arrays.sort(sorted);\n\n // Zeitmessung für das unsortierte Array\n long start = System.nanoTime();\n long sumUnsorted = processArray(unsorted);\n long end = System.nanoTime();\n System.out.println("Unsortiert: " + (end - start) / 1_000_000 + " ms, Summe = " + sumUnsorted);\n\n // Zeitmessung für das sortierte Array\n start = System.nanoTime();\n long sumSorted = processArray(sorted);\n end = System.nanoTime();\n System.out.println("Sortiert: " + (end - start) / 1_000_000 + " ms, Summe = " + sumSorted);\n }\n\n private static long processArray(int[] arr) {\n long sum = 0;\n for (int i = 0; i < arr.length; i++) {\n if (arr[i] >= THRESHOLD) {\n sum += arr[i];\n }\n }\n return sum;\n }\n}\n\n\nErgebnisse auf meinem Laptop (Intel i7, Java 17):
\n- \n
- Unsortiertes Array: ~22 ms \n
- Sortiertes Array: ~8 ms \n
Fast ein 3-facher Unterschied! Warum? Die Anzahl der Operationen ist doch gleich. Die Antwort liegt darin, wie der Prozessor die Bedingung if (arr[i] >= THRESHOLD) verarbeitet.
2. Branch Prediction: Das Gehirn des Prozessors
\n\nModerne Prozessoren verwenden eine Pipeline-Architektur – sie sagen voraus, welcher Ausführungspfad (Zweig) gewählt wird, und beginnen, Anweisungen im Voraus auszuführen. Ist die Vorhersage richtig – läuft alles rund. Ist sie falsch – wird die Pipeline zurückgesetzt und der Prozessor verbraucht Taktzyklen für die Bereinigung.
\n\nIn einem sortierten Array sind die Werte zunächst lange kleiner als 128 und dann größer. Der Prozessor „lernt“ schnell: Wenn die letzten 1000 Male false war, ist das nächste Mal wahrscheinlich auch false. Die Vorhersage funktioniert nahezu perfekt.
In einem unsortierten Array springen die Werte chaotisch über und unter den Schwellenwert. Der Prozessor irrt sich in etwa 50 % der Fälle, was