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.

-- 
──────────────────────────────────────────────────────────────────────────
 Dr. Matthias Kretz                           https://mattkretz.github.io
 GSI Helmholtz Center for Heavy Ion Research               https://gsi.de
 std::simd
──────────────────────────────────────────────────────────────────────────

Reply via email to