C++ vs. Assembler: Warum der Compiler handgeschriebenen Code übertrifft

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum "denkt" der Computer schneller als der Programmierer?

\n\n

Stellen Sie sich vor: Sie haben einen Abend damit verbracht, die Collatz-Folge für die Zahl 27 auf ein Blatt Papier zu schreiben. Und dann hat Ihr Kollege eine C++-Schleife geschrieben, die in einer Sekunde eine Milliarde Zahlen durchläuft. Sind Sie wütend? Warten Sie, das ist erst der Anfang.

\n\n

Im Jahr 2025 ist auf Stack Overflow eine alte, aber sehr aufschlussreiche Frage neu aufgeflammt: "Why does C++ code for testing the Collatz conjecture run faster than hand-written assembly?". Man sollte meinen, Assembler sei der reine Dialog mit dem Prozessor, ohne Vermittler. Wie kann es sein, dass ein hochsprachlicher C++-Code Assembler übertrifft, der von einem Menschen geschrieben wurde?

\n\n

Lassen Sie uns dieses Paradoxon am Beispiel der Collatz-Vermutung (auch bekannt als 3n+1-Problem) analysieren. Sie werden erfahren, warum moderne C++-Compiler wahre Zeitmaschinen sind und wie man im Jahr 2025 Code schreibt, der schneller ist als die "manuelle" Registerverwaltung.

\n\n

Was ist die Collatz-Vermutung und was hat das mit C++ zu tun?

\n\n

Zur Erinnerung: Man nehme eine beliebige natürliche Zahl n. Ist sie gerade, teilt man sie durch 2. Ist sie ungerade, multipliziert man sie mit 3 und addiert 1. Die Vermutung besagt, dass jede Zahl früher oder später bei 1 ankommt. Die Aufgabe des Programmierers ist es, dies für einen möglichst großen Zahlenbereich zu überprüfen.

\n\n

Eine klassische Implementierung in C++ sieht so aus:

\n\n
#include <iostream>\n\nint collatz_steps(long long n) {\n    int steps = 0;\n    while (n != 1) {\n        if (n % 2 == 0) {\n            n /= 2;\n        } else {\n            n = 3 * n + 1;\n        }\n        ++steps;\n    }\n    return steps;\n}\n\nint main() {\n    long long max_steps = 0;\n    long long best_num = 0;\n    for (long long i = 1; i <= 1000000; ++i) {\n        int s = collatz_steps(i);\n        if (s > max_steps) {\n            max_steps = s;\n            best_num = i;\n        }\n    }\n    std::cout << "Zahl: " << best_num << " Schritte: " << max_steps;\n}\n
\n\n

Sieht eigentlich nicht kompliziert aus. Aber genau dieser einfache Code hat eine legendäre Debatte mit Assembler ausgelöst.

\n\n

Warum verliert Assembler? Die Hauptfehler im Jahr 2025

\n\n

Wenn ein Entwickler in Assembler schreibt, denkt er oft: "Ich weiß, wie der Prozessor funktioniert, ich schreibe es perfekt." Aber moderne Prozessoren sind nicht einfach Befehlsausführer. Sie sind komplexe superskalare Pipelines mit Sprungvorhersage, spekulativer Ausführung und Cache-Speicher. Der C++-Compiler (GCC, Clang, MSVC) weiß alles über seine Zielarchitektur.

\n\n

Fehler Nr. 1: Ignorieren der Verzweigungsoptimierung

\n\n

Im manuellen Assembler schreibt der Programmierer Verzweigungen oft "geradeheraus":

\n
; Pseud-Assembler\ncheck_even:\n    test rax, 1\n    jnz is_odd\n    shr rax, 1\n    jmp next\nis_odd:\n    mov rbx, rax\n    shl rax, 1\n    add rax, rbx\n    inc rax\nnext:\n    ...
\n\n

Der C++-Compiler mit dem Flag -O3 macht es raffinierter. Er verwendet CMOV (conditional move) und Bit-Magie. Statt Verzweigung kommt Arithmetik:

\n\n
; GCC 14 wird so etwas generieren:\n    mov     rcx, rax\n    shr     rcx, 1          ; durch 2 teilen (wenn gerade)\n    and     eax, 1          ; niederwertigstes Bit prüfen\n    lea     rdx, [rax + rcx*2 + 1] ; raffinierte Berechnung für ungerade\n    test    eax, eax\n    cmove   rax, rcx        ; wenn gerade - Division nehmen\n    cmovne  rax, rdx        ; wenn ungerade - 3n+1 nehmen\n
\n\n

Ergebnis: Keine Pipeline-Stalls aufgrund falsch vorhergesagter Sprünge. Im Jahr 2025 ist dies kritisch, da der Sprungvorhersager in der CPU zwar intelligenter geworden ist, aber für Zufallszahlen immer noch nicht perfekt ist.

\n\n

Fehler Nr. 2: Vektorisierung vergessen (AVX-512)

\n\n

Moderne C++-Compiler (insbesondere Clang 18+ und GCC 14+) vektorisieren Schleifen automatisch. Wenn Sie eine einfache Prüfschleife schreiben, kann der Compiler bis zu 8 Zahlen pro Takt verarbeiten, wobei er

Blogs

Buchempfehlungen