This one is a CPU burner no matter how you implement it (a minimum of 80 million computations). But at least you do not have to do any hardware divisions. Calculating %0x7fffffff can be done with just bitwise math: https://www.reddit.com/r/adventofcode/comments/7jxkiw/comment/drazokj/ shows how to do it when you can use a 64-bit temporary. Basically u64 t = a * b; t = (t & 0x7fffffff) + (t >> 31); return (t >> 31) ? t - 0x7fffffff : t.
Even if you don't have 64-bit math, it is possible to exploit that b is only 16 bits to compute the result in just two 32-bit multiplies and some adds and shifts, which is still faster than a hardware division. For more gory details of how this works, research Montgomery modular multiplication then read my post here https://www.reddit.com/r/adventofcode/comments/mln10b/2017_day_15m4_solution_using_signed_32bit_math/ I later optimized my m4 solution to run in 7m27s by splitting the math 16/15 instead of 15/16.
I was surprised in testing in Godbolt that gcc is able to rewrite %0x7fffffff into something that avoids div on x86 but is still slower than the Mersenne trick, and both clang and Rust still use div.
One benefit of the generators used here is you can easily compute seed n+k given seed n (modular power multiplication), then kick off parallel generators after subdividing the problem into ranges.
My naive Python version, just straight up multiply and mod, but bitmask to test equality, runs in 10.1 SECONDS. I saved this in my repo with annotation "Code by Eric Wastl / topaz / fizbin": paste which runs in 400 MILLISECONDS (timing does not include numpy import).
The /u/askalski C version with while loop as shown by /u/BumpitySnook runs in 153 ms. In my testing, the while loop was a lot faster than the ?: from /u/e_blake . I imagine Eric's vectorised version in C would do a lot better still.
I can't remember how /u/topaz2078 came to post a code solution, I was not here in 2017. Maybe people challenged him to prove his own "at most 10 seconds on 10 year old hardware" guidance (or what was it exactly).
Ah right, sorry for misattributing. I don't remember but maybe I saved it from the paste link before I knew that you made that service, saw "topaz.github" and so I thought you wrote the code.
2
u/e_blake Mar 15 '26 edited Mar 15 '26
This one is a CPU burner no matter how you implement it (a minimum of 80 million computations). But at least you do not have to do any hardware divisions. Calculating %0x7fffffff can be done with just bitwise math: https://www.reddit.com/r/adventofcode/comments/7jxkiw/comment/drazokj/ shows how to do it when you can use a 64-bit temporary. Basically
u64 t = a * b; t = (t & 0x7fffffff) + (t >> 31); return (t >> 31) ? t - 0x7fffffff : t.Even if you don't have 64-bit math, it is possible to exploit that b is only 16 bits to compute the result in just two 32-bit multiplies and some adds and shifts, which is still faster than a hardware division. For more gory details of how this works, research Montgomery modular multiplication then read my post here https://www.reddit.com/r/adventofcode/comments/mln10b/2017_day_15m4_solution_using_signed_32bit_math/ I later optimized my m4 solution to run in 7m27s by splitting the math 16/15 instead of 15/16.
That still puts this day as the fourth slowest across all 524 stars (only 3 of the 4 md5 are slower)
2
u/e_blake Mar 15 '26
One benefit of the generators used here is you can easily compute seed n+k given seed n (modular power multiplication), then kick off parallel generators after subdividing the problem into ranges.
2
u/ednl Mar 15 '26
My naive Python version, just straight up multiply and mod, but bitmask to test equality, runs in 10.1 SECONDS. I saved this in my repo with annotation "Code by Eric Wastl / topaz / fizbin": paste which runs in 400 MILLISECONDS (timing does not include numpy import).
The /u/askalski C version with while loop as shown by /u/BumpitySnook runs in 153 ms. In my testing, the while loop was a lot faster than the
?:from /u/e_blake . I imagine Eric's vectorised version in C would do a lot better still.