From a2ed301738a56ac31a1c086680ed823a7b3dbfc7 Mon Sep 17 00:00:00 2001 From: Fikret Ardal Date: Fri, 23 Oct 2026 12:30:44 -0400 Subject: [PATCH] Fix AVX-512 reduce_max/reduce_min missing bytes 32-39 for 8-bit types The avx512f kernels folded the upper 256 bits into the lower half with _mm512_permutexvar_epi64 using indices {5, 6, 7, 8, ...}. The intrinsic only uses the low 3 bits of each index, so 8 wraps to 0 and qword 4 (bytes 32-39) was never compared. An extremum located there was missed. Take the max/min of the two 256-bit halves directly (lower_half / upper_half) and continue with the AVX2 reduction. This also drops the 512-bit max whose upper half was discarded, and it extends the kernel to 16-bit types, which previously used the generic swizzle reduction (up to 4x slower with GCC). Add a test that places the extremum at each position of every batch type. Assisted-By: Claude Opus 5.5 --- include/xsimd/arch/xsimd_avx512f.hpp | 18 ++++++------------ test/test_batch.cpp | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/include/xsimd/arch/xsimd_avx512f.hpp b/include/xsimd/arch/xsimd_avx512f.hpp index 06da6218e..414a26f34 100644 --- a/include/xsimd/arch/xsimd_avx512f.hpp +++ b/include/xsimd/arch/xsimd_avx512f.hpp @@ -1962,25 +1962,19 @@ namespace xsimd } // reduce_max - template > + template > XSIMD_INLINE T reduce_max(batch const& self, requires_arch) noexcept { - constexpr batch_constant mask; - batch step = _mm512_permutexvar_epi64(mask.as_batch(), self); - batch acc = max(self, step); - __m256i low = _mm512_castsi512_si256(acc); - return reduce_max(batch(low)); + batch low(detail::lower_half(self)), high(detail::upper_half(self)); + return reduce_max(max(low, high)); } // reduce_min - template > + template > XSIMD_INLINE T reduce_min(batch const& self, requires_arch) noexcept { - constexpr batch_constant mask; - batch step = _mm512_permutexvar_epi64(mask.as_batch(), self); - batch acc = min(self, step); - __m256i low = _mm512_castsi512_si256(acc); - return reduce_min(batch(low)); + batch low(detail::lower_half(self)), high(detail::upper_half(self)); + return reduce_min(min(low, high)); } // reduce_mul diff --git a/test/test_batch.cpp b/test/test_batch.cpp index 0bee8b1fc..3ee94ee4f 100644 --- a/test/test_batch.cpp +++ b/test/test_batch.cpp @@ -1070,6 +1070,22 @@ struct batch_test INFO("reduce_min"); CHECK_SCALAR_EQ(res, expected); } + // reduce_max / reduce_min with the extremum at each position + for (std::size_t i = 0; i < size; ++i) + { + array_type data; + data.fill(value_type(1)); + { + data[i] = std::numeric_limits::max(); + INFO("reduce_max, extremum at index ", i); + CHECK_SCALAR_EQ(reduce_max(batch_type::load_unaligned(data.data())), data[i]); + } + { + data[i] = std::numeric_limits::lowest(); + INFO("reduce_min, extremum at index ", i); + CHECK_SCALAR_EQ(reduce_min(batch_type::load_unaligned(data.data())), data[i]); + } + } // reduce_mul { value_type expected = std::accumulate(lhs.cbegin(), lhs.cend(), value_type(1), std::multiplies());