On Tue, 11 Aug 2026 at 15:51, Matthias Kretz <[email protected]> wrote:
>
> Jonathan Wakely [Thursday, 30 July 2026, 18:25:53 CEST]:
> > bool is_leap_neri(short y)
> > {
> >     return (y & (y % 25 == 0 ? 15 : 3)) == 0;
> > }
>
> This can be vectorized as 16-bit integers => 32 is_leap evaluations in
> parallel with AVX2.
>
> > bool is_leap_hueffner(short y)
> > {
> >     const auto y32 = static_cast<unsigned int>(y) + 32800u;
> >     return ((y32 * 1073750999u) & 3221352463u) <= 126976u;
> > }
>
> This requires 32-bit integers => 16 is_leap evaluations in parallel with AVX2.
>
> Consequently, vectorization provides a 2x *throughput* advantage for neri over
> hueffner. But throughput is only one performance aspect. Latency is often more
> important. And if I understand your benchmark results correctly (and simply
> because of the `% 25` in neri), hueffner wins the latency benchmark.
>
> With std::simd we could have both:
>
> template <typename Abi>
> auto is_leap(simd::basic_vec<short, Abi> y)
> {
>   if constexpr (y.size() <= simd::vec<int>::size())
>     {
>       simd::rebind_t<unsigned int, simd::basic_vec<short, Abi>> y32 = y;
>       y += 32800u;
>       return ((y32 * 1073750999u) & 3221352463u) <= 126976u;
>     }
>   else
>     {
>       return (y & (y % cw<25> == cw<0> ? cw<15> : cw<3>)) == cw<0>;
>     }
> }
>
> (cw<n> is used because we don't have short literals; alternative: short(n))
>
> Actually, the first branch might be more efficient in throughput even for up
> to simd::vec<int>::size() * 2 or more, because of ILP.
>
> Anyway, I'm derailing the topic :-). I believe the proposed change makes sense
> if it improves latency. It might be a good idea to add a comment that
> throughput is better with Neri's algorithm but that we optimize for latency.

I think that comment would be good. And I think optimizing for latency
is the right choice. I think it's more likely that we will have
occasional computations that depend on is_leap() and so we want each
one to be as fast as possible in isolation. As Cassio also said, I
expect code like the benchmark to be less common, i.e. crunching huge
numbers of years (or other date times) where is_leap throughput
matters more. And the new algorithm still isn't *slow* for the case of
crunching huge numbers of years, so it's still super good enough for
the cases that care more about throughput.

Thanks to everybody for the additional work verifying this. Let's go
ahead and make the change.

Reply via email to