- Es wird eine Funktion implementiert, die ein Schaltjahr mit nur drei CPU-Befehlen bestimmt
- Diese Methode arbeitet mit Bit-Operationen und Multiplikation ohne traditionelle Verzweigungen
- Dieser Ansatz ist im Bereich von 0 bis 102499 Jahren korrekt
- Benchmark-Ergebnisse zeigen gegenüber herkömmlichen Methoden eine rund 3,8-fach höhere Leistung
Überblick und Problemstellung
- Für Jahre zwischen 0 und 102499 eine Funktion, die mit nur drei CPU-Befehlen ein Schaltjahr bestimmt
- Die tatsächlich verwendete Funktion hat die Form
((y * 1073750999) & 3221352463) <= 126976
- Erläutert werden Prinzip, Funktionsweise und praktischer Nutzen dieser Bit-Twiddling-Technik
Traditionelle Methode zur Schaltjahr-Prüfung
- Üblicherweise wird die Schaltjahr-Prüfung mit Divisionen (modulo) und bedingten Verzweigungen implementiert
Optimierung des Standardansatzes
- Die Prüfung
(y % 100) kann durch (y % 25) ersetzt werden, und (y % 400) durch (y % 16)
- Der Grund ist, dass zuvor bereits durch 4 geteilt wurde und sich die Beziehung daher auf 25 bzw. 16 umstellen lässt
- Eine magische Konstante, um die Operation
(y % 25) ohne Division in Multiplikation und Vergleich umzuwandeln
- Beispiel: Sie kann in
x * 3264175145 > 171798691 umgeformt werden
- Durch zusätzliche Bitmasken lassen sich Divisionen durch 4 oder 16 durch
(y & 3) bzw. (y & 15) ersetzen
- Compiler automatisieren solche Umformungen, man kann sie aber auch direkt in anderen Sprachen nutzen
Branchless-Implementierung
Finden der magischen Konstanten: Bit-Twiddling-Ansatz
- Um die Schaltjahr-Formel noch einfacher zu machen, werden kombinatorische Suche und der SMT-Solver Z3 genutzt
- Mit Z3 werden Konstanten
f, m, t gesucht, die die Anforderungen erfüllen
- Es werden Werte gefunden, die im Bereich 0 bis 102499 korrekte Ergebnisse liefern
- Das Endergebnis ist
(y * 1073750999) & 3221352463 <= 126976
Erklärung des Prinzips und der internen Struktur der Funktion
- Die Konstanten werden binär analysiert und in drei wesentliche Bit-Bereiche A, B, C unterteilt
- Je nach Bitzustand in diesen Bereichen werden die drei Bedingungen zur Schaltjahr-Prüfung abgedeckt
- Logische Zerlegung der Funktion:
- Bereich A: Prüfung der Bedingung für Vielfache von 4, einschließlich
(y % 4) != 0
- Bereich B: Filtert
(y % 100) != 0 über verschiedene Muster, etwa Endziffern wie 14, 57 oder 71
- Bereich C: Prüfung von
(y % 16) == 0, also ob ein Vielfaches von 16 vorliegt
- Erläutert wird, warum die Multiplikation tatsächlich verschiedene Bedingungen ohne echte Restwertberechnung kombiniert
- Beim Multiplizieren mit der magischen Konstante entstehen bei Vielfachen von 100 und ähnlichen Werten charakteristische Bitmuster
- Zusätzlich wird die mathematische innere Struktur analysiert, einschließlich Multiplikationsfehlern und verschiedener Ziffernmuster
Erweiterbarkeit auf größere Bereiche und Bitbreiten
- Für eine Erweiterung auf 64 Bit wird ebenfalls eine Methode zur Suche nach geeigneten Kombinationen magischer Konstanten vorgestellt
- Durch Variieren von
f, m, t lässt sich der größtmögliche Bereich finden
- Auch auf StackExchange gibt es Beispiele für optimale Kombinationen und Beweise mit Z3
Benchmarks und tatsächlicher Leistungsvergleich
- Benchmark-Ergebnisse:
- Bei vorhersagbaren Werten wie 2025 gibt es mit 0,65 bis 0,69 ns praktisch kaum Unterschiede
- Bei zufälligen Eingaben zeigt
is_leap_year_fast eine etwa 3,8-fach höhere Leistung
- Je nach Eingabemuster gibt es deutliche Vorteile, wenn ein verzweigender Ansatz schlecht vorhersagbar ist
Fazit und praktische Einsetzbarkeit
- In realen Anwendungen ist der Nutzen bei vorhersagbaren Werten gering, aber in Situationen mit großen Mengen zufälliger Daten sehr nützlich
- Für den Austausch von Standardbibliotheken etwa in Python oder C# sind realistische Benchmarks des Gesamtprogramms nötig
- Die Idee und die Implementierung selbst sind interessant und in bestimmten Situationen eine leistungsstarke Lösung
2 Kommentare
Erinnert an Fast inverse sqrt
Hacker-News-Kommentare
is_leap_year1automatisch zu etwas wieis_leap_year2optimieren, daher scheint es auf C-Quellcodeebene meist nicht mehr nötig zu sein, selbst zu optimieren; in anderen Sprachen kann das aber weiterhin nützlich sein, und besonders beeindruckend ist, wie schlicht die aktuelle Version descal-Programms die Schaltjahr-Prüfung behandelt.((y * 1073750999) & 3221352463) <= 126976funktioniert, zwangsläufig kompliziert ist; eigentlich ist das nur folgerichtig.CMP(X, Y)-Makro, einem Beispiel zur Optimierung dersignum-Funktion, Assembly-Code-Beispielen für den Motorola 68000 sowie einer Sammlung von Bit-Makros aus OpenSolaris. Bedauert wurde auch, dass der Open-Solaris-Blog verschwunden ist; für alle mit Interesse an Code-Optimierung trotzdem sehr empfehlenswert.