Mit drei Befehlen prüfen, ob ein Jahr ein Schaltjahr ist
(hueffner.de)- 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
- Die tatsächlich verwendete Funktion hat die Form
- 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
- Geprüft wird, ob die Zahl durch 4 teilbar ist, nicht durch 100 teilbar ist oder durch 400 teilbar ist
- Beispielcode:
if ((y % 4) != 0) return false if ((y % 100) != 0) return true if ((y % 400) == 0) return true return false
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 > 171798691umgeformt werden
- Beispiel: Sie kann in
- 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
- Sie lässt sich auch in eine branchless Form umwandeln:
return !(y & ((y % 25) ? 3 : 15)) - Solche Ansätze eignen sich gut für Code Golf, bei dem es um möglichst kurzen Code geht
Finden der magischen Konstanten: Bit-Twiddling-Ansatz
- Um die Schaltjahr-Formel noch einfacher zu machen, werden kombinatorische Suche und der SMT-Solver Z3 genutzt
- Form:
((y * f) & m) <= t
- Form:
- Mit Z3 werden Konstanten
f,m,tgesucht, 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
- Bereich A: Prüfung der Bedingung für Vielfache von 4, einschließlich
- 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,tlässt sich der größtmögliche Bereich finden - Auch auf StackExchange gibt es Beispiele für optimale Kombinationen und Beweise mit Z3
- Durch Variieren von
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_fasteine 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.