Einleitung: Ein Mythos, der seit Jahrzehnten lebt
\n\nWenn Sie jemals auf Stack Overflow im Bereich C++ unterwegs waren, haben Sie sicherlich die Frage gesehen: „Is < faster than <=?“. Diese Frage hat tausende Aufrufe und Dutzende Diskussionsseiten gesammelt. Und das ist kein Wunder – das Thema Optimierung von Code bewegt die Gemüter von Programmierern aller Erfahrungsstufen.
\n\nDie Logik scheint bombenfest: Der Operator < vergleicht zwei Zahlen und gibt ein Ergebnis zurück, während <= dasselbe tut, aber zusätzlich auf Gleichheit prüft. Also muss < schneller sein, oder? Besonders wichtig ist dies für Schleifen, wo die Vergleichsoperation millionenfach ausgeführt wird.
Heute werden wir diesen Mythos ein für alle Mal klären. Sie erfahren:
\n- \n
- Wie die Operatoren auf Prozessorebene tatsächlich arbeiten. \n
- Warum der Compiler Ihr bester Freund (oder Feind, wenn Sie ihm nicht vertrauen) ist. \n
- Wann Mikrooptimierung sinnvoll ist und wann sie Zeitverschwendung ist. \n
Wie arbeiten die Operatoren < und <= auf Assembler-Ebene?
\n\nUm zu verstehen, ob es einen Unterschied gibt, müssen wir „unter die Haube“ schauen – in das, was der Compiler aus Ihrem C++-Code macht. Betrachten wir zwei einfache Funktionen:
\n\nbool less_than(int a, int b) {\n return a < b;\n}\n\nbool less_or_equal(int a, int b) {\n return a <= b;\n}\n\nKompilieren wir sie mit g++ (ohne Optimierungen, Flag -O0). Folgendes sehen wir im Assembler-Listing (x86-64):
less_than(int, int):\n cmp edi, esi ; a und b vergleichen\n setl al ; al=1 setzen wenn a < b (kleiner)\n ret\n\nless_or_equal(int, int):\n cmp edi, esi ; a und b vergleichen\n setle al ; al=1 setzen wenn a <= b (kleiner oder gleich)\n ret\n\nSehen Sie? Der einzige Unterschied sind die Befehle setl (set if less) und setle (set if less or equal). Beide Befehle werden in einem Prozessortakt ausgeführt. Auf modernen CPUs (Intel Core i7, AMD Ryzen) haben sie dieselbe Latenz – 1 Zyklus.
Fazit Nr. 1: Auf Maschinenbefehlsebene gibt es keinen Leistungsunterschied zwischen < und <=. Punkt.
Und was ist mit Schleifen? Hier muss es doch einen Unterschied geben!
\n\nMeistens entbrennt die Debatte um Schleifen. Vergleichen Sie zwei Ansätze:
\n\n// Variante 1: mit <\nfor (int i = 0; i < n; ++i) {\n // etwas tun\n}\n\n// Variante 2: mit <=\nfor (int i = 0; i <= n - 1; ++i) {\n // dasselbe tun\n}\n\nDie Intuition sagt: Die zweite Variante macht eine überflüssige Berechnung n - 1. Aber schauen wir, was nach der Compiler-Optimierung passiert.
Starten wir einen Benchmark mit g++ -O2 für n = 1 000 000 000:
\n\n#include <iostream>\n#include <chrono>\n\nint main() {\n const long long n = 1'000'000'000;\n volatile long long sink = 0; // damit der Compiler die Schleife nicht wegwirft\n\n auto start = std::chrono::high_resolution_clock::now();\n \n for (long long i = 0; i < n; ++i) {\n sink += i;\n }\n \n auto end = std::chrono::high_resolution_clock::now();\n std::cout << "Zeit (<): " \n << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()\n << " ms\\n";\n\n start = std::chrono::high_resolution_clock::now();\n \n for (long long i = 0; i <= n - 1; ++i) {\n sink += i;\n }\n \n end = std::chrono::high_resolution_clock::now();\n std::cout << "Zeit (<=): " \n << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()\n << " ms\\n";\n\n return 0;\n}\n\nErgebnisse auf meinem Rechner (i7-107