This is an automated email from the ASF dual-hosted git repository.

pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 36f655f44cc GH-51476: [C++][Parquet] Vectorise FindMinMax explicitly 
(#51478)
36f655f44cc is described below

commit 36f655f44cc4fa63c389a35b0bcfbe6723c0f87d
Author: Dominique Belhachemi <[email protected]>
AuthorDate: Tue Oct 6 04:56:00 2026 -0400

    GH-51476: [C++][Parquet] Vectorise FindMinMax explicitly (#51478)
    
    ### Rationale for this change
    
    FindMinMaxAvx2 in parquet/level_comparison_avx2.cc was not vectorised.
    
    ### What changes are included in this PR?
    
    A single xsimd kernel `FindMinMaxSimd<Arch>` in 
`level_comparison_simd_kernel_internal.h`,
    plus support for multiple architectures.
    
    ### Are these changes tested?
    
    Yes, with a nice speedup.
    | dispatch level | time |
    |---|---|
    | NONE | 513 ns |
    | SSE4_2 | 54.9 ns (9.3x) |
    | AVX2 | 28.7 ns (17.9x) |
    
    ### Are there any user-facing changes?
    
    No
    
    ### Was AI used for this PR?
    
    **PR code and description written by:**
    
    - [x] Human
    - [x] AI
    
    **Reviewed before submission by:**
    
    - [x] Human
    - [x] AI
    - [ ] Not reviewed
    
    * GitHub Issue: #51476
    
    Authored-by: Dominique Belhachemi <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/src/parquet/CMakeLists.txt                     |  9 ++++++
 cpp/src/parquet/level_comparison.cc                |  6 ++--
 cpp/src/parquet/level_comparison.h                 |  2 +-
 cpp/src/parquet/level_comparison_avx2.cc           |  4 ---
 cpp/src/parquet/level_comparison_avx2_internal.h   |  1 -
 ...l_comparison.h => level_comparison_simd_128.cc} | 33 ++++++++++++----------
 ...l_comparison.h => level_comparison_simd_256.cc} | 31 ++++++++++----------
 ...internal.h => level_comparison_simd_internal.h} | 11 +++++++-
 ...l.h => level_comparison_simd_kernel_internal.h} | 31 ++++++++++++++++++--
 cpp/src/parquet/level_conversion_benchmark.cc      | 20 +++++++++++++
 10 files changed, 107 insertions(+), 41 deletions(-)

diff --git a/cpp/src/parquet/CMakeLists.txt b/cpp/src/parquet/CMakeLists.txt
index 5810317a959..a4091a80d3b 100644
--- a/cpp/src/parquet/CMakeLists.txt
+++ b/cpp/src/parquet/CMakeLists.txt
@@ -193,6 +193,15 @@ set(PARQUET_SRCS
     stream_writer.cc
     types.cc)
 
+# guards itself: Neon on aarch64, SSE4.2 on x86, empty elsewhere
+list(APPEND PARQUET_SRCS level_comparison_simd_128.cc)
+if(ARROW_CPU_FLAG STREQUAL "x86" AND ARROW_HAVE_RUNTIME_SSE4_2)
+  set_source_files_properties(level_comparison_simd_128.cc
+                              PROPERTIES COMPILE_OPTIONS 
"${ARROW_SSE4_2_FLAG}")
+endif()
+
+append_runtime_avx2_src(PARQUET_SRCS level_comparison_simd_256.cc)
+
 if(ARROW_HAVE_RUNTIME_AVX2)
   # AVX2 is used as a proxy for BMI2.
   list(APPEND
diff --git a/cpp/src/parquet/level_comparison.cc 
b/cpp/src/parquet/level_comparison.cc
index 81e42445853..beb9c66c5f9 100644
--- a/cpp/src/parquet/level_comparison.cc
+++ b/cpp/src/parquet/level_comparison.cc
@@ -23,6 +23,8 @@
 #  include "parquet/level_comparison_avx2_internal.h"
 #endif
 
+#include "parquet/level_comparison_simd_internal.h"
+
 #define PARQUET_IMPL_NAMESPACE standard
 #include "parquet/level_comparison_inc.h"
 #undef PARQUET_IMPL_NAMESPACE
@@ -37,8 +39,6 @@ using ::arrow::internal::DispatchLevel;
 using ::arrow::internal::DynamicDispatch;
 using ::arrow::internal::DynamicDispatchTarget;
 
-// defined in level_comparison_avx2.cc
-
 struct GreaterThanDynamicFunction {
   using FunctionType = decltype(&GreaterThanBitmap);
 
@@ -56,6 +56,8 @@ struct MinMaxDynamicFunction {
   static constexpr auto targets() {
     return std::array{
         ARROW_DISPATCH_TARGET_NONE(&standard::FindMinMaxImpl)  //
+        ARROW_DISPATCH_TARGET_NEON(&FindMinMaxNeon)            //
+        ARROW_DISPATCH_TARGET_SSE4_2(&FindMinMaxSse42)         //
         ARROW_DISPATCH_TARGET_AVX2(&FindMinMaxAvx2)            //
     };
   }
diff --git a/cpp/src/parquet/level_comparison.h 
b/cpp/src/parquet/level_comparison.h
index 3ae442dd46e..966b21fad37 100644
--- a/cpp/src/parquet/level_comparison.h
+++ b/cpp/src/parquet/level_comparison.h
@@ -33,6 +33,6 @@ struct MinMax {
   int16_t max;
 };
 
-MinMax FindMinMax(const int16_t* levels, int64_t num_levels);
+MinMax PARQUET_EXPORT FindMinMax(const int16_t* levels, int64_t num_levels);
 
 }  // namespace parquet::internal
diff --git a/cpp/src/parquet/level_comparison_avx2.cc 
b/cpp/src/parquet/level_comparison_avx2.cc
index db8935d4037..12cb122a942 100644
--- a/cpp/src/parquet/level_comparison_avx2.cc
+++ b/cpp/src/parquet/level_comparison_avx2.cc
@@ -28,9 +28,5 @@ uint64_t GreaterThanBitmapAvx2(const int16_t* levels, int64_t 
num_levels, int16_
   return avx2::GreaterThanBitmapImpl(levels, num_levels, rhs);
 }
 
-MinMax FindMinMaxAvx2(const int16_t* levels, int64_t num_levels) {
-  return avx2::FindMinMaxImpl(levels, num_levels);
-}
-
 }  // namespace internal
 }  // namespace parquet
diff --git a/cpp/src/parquet/level_comparison_avx2_internal.h 
b/cpp/src/parquet/level_comparison_avx2_internal.h
index 3f68a6ff05d..d95fb856419 100644
--- a/cpp/src/parquet/level_comparison_avx2_internal.h
+++ b/cpp/src/parquet/level_comparison_avx2_internal.h
@@ -23,7 +23,6 @@
 
 namespace parquet::internal {
 
-MinMax FindMinMaxAvx2(const int16_t* levels, int64_t num_levels);
 uint64_t GreaterThanBitmapAvx2(const int16_t* levels, int64_t num_levels, 
int16_t rhs);
 
 }  // namespace parquet::internal
diff --git a/cpp/src/parquet/level_comparison.h 
b/cpp/src/parquet/level_comparison_simd_128.cc
similarity index 57%
copy from cpp/src/parquet/level_comparison.h
copy to cpp/src/parquet/level_comparison_simd_128.cc
index 3ae442dd46e..af5f5f882ad 100644
--- a/cpp/src/parquet/level_comparison.h
+++ b/cpp/src/parquet/level_comparison_simd_128.cc
@@ -14,25 +14,28 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#pragma once
 
-#include <algorithm>
-#include <cstdint>
+#if defined(ARROW_HAVE_NEON)
+#  define FIND_MIN_MAX_PLATFORM FindMinMaxNeon
+#  define ARCH_PLATFORM xsimd::neon64
+#elif defined(ARROW_HAVE_SSE4_2) || defined(ARROW_HAVE_RUNTIME_SSE4_2)
+#  define FIND_MIN_MAX_PLATFORM FindMinMaxSse42
+#  define ARCH_PLATFORM xsimd::sse4_2
+#endif
 
-#include "parquet/platform.h"
+#if defined(FIND_MIN_MAX_PLATFORM)
 
-namespace parquet::internal {
-
-/// Builds a  bitmap where each set bit indicates the corresponding level is 
greater
-/// than rhs.
-uint64_t PARQUET_EXPORT GreaterThanBitmap(const int16_t* levels, int64_t 
num_levels,
-                                          int16_t rhs);
+#  include "parquet/level_comparison_simd_internal.h"
+#  include "parquet/level_comparison_simd_kernel_internal.h"
 
-struct MinMax {
-  int16_t min;
-  int16_t max;
-};
+namespace parquet::internal {
 
-MinMax FindMinMax(const int16_t* levels, int64_t num_levels);
+MinMax FIND_MIN_MAX_PLATFORM(const int16_t* levels, int64_t num_levels) {
+  return FindMinMaxSimd<ARCH_PLATFORM>(levels, num_levels);
+}
 
 }  // namespace parquet::internal
+
+#  undef ARCH_PLATFORM
+#  undef FIND_MIN_MAX_PLATFORM
+#endif  // FIND_MIN_MAX_PLATFORM
diff --git a/cpp/src/parquet/level_comparison.h 
b/cpp/src/parquet/level_comparison_simd_256.cc
similarity index 61%
copy from cpp/src/parquet/level_comparison.h
copy to cpp/src/parquet/level_comparison_simd_256.cc
index 3ae442dd46e..024353dbc85 100644
--- a/cpp/src/parquet/level_comparison.h
+++ b/cpp/src/parquet/level_comparison_simd_256.cc
@@ -14,25 +14,26 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-#pragma once
 
-#include <algorithm>
-#include <cstdint>
+#if defined(ARROW_HAVE_RUNTIME_AVX2)
+#  define FIND_MIN_MAX_PLATFORM FindMinMaxAvx2
+#  define ARCH_PLATFORM xsimd::avx2
+#endif
 
-#include "parquet/platform.h"
+#if !defined(FIND_MIN_MAX_PLATFORM)
+#  error "This file must be compiled with a known SIMD micro architecture"
+#endif
 
-namespace parquet::internal {
-
-/// Builds a  bitmap where each set bit indicates the corresponding level is 
greater
-/// than rhs.
-uint64_t PARQUET_EXPORT GreaterThanBitmap(const int16_t* levels, int64_t 
num_levels,
-                                          int16_t rhs);
+#include "parquet/level_comparison_simd_internal.h"
+#include "parquet/level_comparison_simd_kernel_internal.h"
 
-struct MinMax {
-  int16_t min;
-  int16_t max;
-};
+namespace parquet::internal {
 
-MinMax FindMinMax(const int16_t* levels, int64_t num_levels);
+MinMax FIND_MIN_MAX_PLATFORM(const int16_t* levels, int64_t num_levels) {
+  return FindMinMaxSimd<ARCH_PLATFORM>(levels, num_levels);
+}
 
 }  // namespace parquet::internal
+
+#undef ARCH_PLATFORM
+#undef FIND_MIN_MAX_PLATFORM
diff --git a/cpp/src/parquet/level_comparison_avx2_internal.h 
b/cpp/src/parquet/level_comparison_simd_internal.h
similarity index 77%
copy from cpp/src/parquet/level_comparison_avx2_internal.h
copy to cpp/src/parquet/level_comparison_simd_internal.h
index 3f68a6ff05d..518ce16f39e 100644
--- a/cpp/src/parquet/level_comparison_avx2_internal.h
+++ b/cpp/src/parquet/level_comparison_simd_internal.h
@@ -23,7 +23,16 @@
 
 namespace parquet::internal {
 
+#if defined(ARROW_HAVE_NEON)
+MinMax FindMinMaxNeon(const int16_t* levels, int64_t num_levels);
+#endif
+
+#if defined(ARROW_HAVE_SSE4_2) || defined(ARROW_HAVE_RUNTIME_SSE4_2)
+MinMax FindMinMaxSse42(const int16_t* levels, int64_t num_levels);
+#endif
+
+#if defined(ARROW_HAVE_RUNTIME_AVX2)
 MinMax FindMinMaxAvx2(const int16_t* levels, int64_t num_levels);
-uint64_t GreaterThanBitmapAvx2(const int16_t* levels, int64_t num_levels, 
int16_t rhs);
+#endif
 
 }  // namespace parquet::internal
diff --git a/cpp/src/parquet/level_comparison_avx2_internal.h 
b/cpp/src/parquet/level_comparison_simd_kernel_internal.h
similarity index 52%
copy from cpp/src/parquet/level_comparison_avx2_internal.h
copy to cpp/src/parquet/level_comparison_simd_kernel_internal.h
index 3f68a6ff05d..5b15a8f77b0 100644
--- a/cpp/src/parquet/level_comparison_avx2_internal.h
+++ b/cpp/src/parquet/level_comparison_simd_kernel_internal.h
@@ -17,13 +17,40 @@
 
 #pragma once
 
+#include <xsimd/xsimd.hpp>
+
+#include <algorithm>
 #include <cstdint>
+#include <limits>
 
 #include "parquet/level_comparison.h"
 
 namespace parquet::internal {
 
-MinMax FindMinMaxAvx2(const int16_t* levels, int64_t num_levels);
-uint64_t GreaterThanBitmapAvx2(const int16_t* levels, int64_t num_levels, 
int16_t rhs);
+template <typename Arch>
+MinMax FindMinMaxSimd(const int16_t* levels, int64_t num_levels) {
+  using batch = xsimd::batch<int16_t, Arch>;
+  constexpr int64_t kLanes = static_cast<int64_t>(batch::size);
+
+  MinMax out{std::numeric_limits<int16_t>::max(), 
std::numeric_limits<int16_t>::min()};
+
+  int64_t i = 0;
+  if (num_levels >= kLanes) {
+    batch vmin(out.min);
+    batch vmax(out.max);
+    for (; i + kLanes <= num_levels; i += kLanes) {
+      const auto v = batch::load_unaligned(levels + i);
+      vmin = xsimd::min(vmin, v);
+      vmax = xsimd::max(vmax, v);
+    }
+    out.min = xsimd::reduce_min(vmin);
+    out.max = xsimd::reduce_max(vmax);
+  }
+  for (; i < num_levels; ++i) {
+    out.min = std::min(levels[i], out.min);
+    out.max = std::max(levels[i], out.max);
+  }
+  return out;
+}
 
 }  // namespace parquet::internal
diff --git a/cpp/src/parquet/level_conversion_benchmark.cc 
b/cpp/src/parquet/level_conversion_benchmark.cc
index f3a4f8095e3..0702b39ec20 100644
--- a/cpp/src/parquet/level_conversion_benchmark.cc
+++ b/cpp/src/parquet/level_conversion_benchmark.cc
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "benchmark/benchmark.h"
+#include "parquet/level_comparison.h"
 #include "parquet/level_conversion.h"
 
 constexpr int64_t kLevelCount = 2048;
@@ -78,3 +79,22 @@ void 
BM_DefinitionLevelsToBitmapRepeatedMostPresent(::benchmark::State& state) {
 }
 
 BENCHMARK(BM_DefinitionLevelsToBitmapRepeatedMostPresent);
+
+void BM_FindMinMax(::benchmark::State& state) {
+  const int64_t num_levels = state.range(0);
+  std::vector<int16_t> levels(/*count=*/static_cast<size_t>(num_levels),
+                              kPresentDefLevel);
+  for (size_t x = 0; x < levels.size(); x++) {
+    if (x % 10 == 0) {
+      levels[x] = kMissingDefLevel;
+    }
+  }
+  for (auto _ : state) {
+    auto min_max = parquet::internal::FindMinMax(levels.data(), num_levels);
+    ::benchmark::DoNotOptimize(min_max);
+  }
+  state.SetItemsProcessed(int64_t(state.iterations()) * num_levels);
+  state.SetBytesProcessed(int64_t(state.iterations()) * num_levels * 
sizeof(int16_t));
+}
+
+BENCHMARK(BM_FindMinMax)->Arg(7)->Arg(1024)->Arg(2047)->Arg(kLevelCount);

Reply via email to