Skip to content

GH-47769: [C++] SVE dynamic dispatch#49756

Open
AntoinePrv wants to merge 19 commits intoapache:mainfrom
AntoinePrv:sve-dispatch
Open

GH-47769: [C++] SVE dynamic dispatch#49756
AntoinePrv wants to merge 19 commits intoapache:mainfrom
AntoinePrv:sve-dispatch

Conversation

@AntoinePrv
Copy link
Copy Markdown
Contributor

@AntoinePrv AntoinePrv commented Apr 15, 2026

Rationale for this change

Just like we dynamically dispatch to AVX2 on x86 CPUs, we want to dynamically dispatch to more advanced SIMD extension on ARM64 chips.

What changes are included in this PR?

  • A new macro to enable selecting the runtime SVE version
  • Detection of the ARM64 CPU features available at runtime
  • Adding SVE to the dynamic dispatch for bit unpacking algorithms.

Are these changes tested?

Are there any user-facing changes?

No.

@AntoinePrv AntoinePrv changed the title Sve dynamic dispatch GH-47769: [C++] Sve dynamic dispatch Apr 15, 2026
@github-actions
Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on GitHub? https://github.com/apache/arrow/issues/new/choose

Opening GitHub issues ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename the pull request title in the following format?

GH-${GITHUB_ISSUE_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@github-actions
Copy link
Copy Markdown

⚠️ GitHub issue #47769 has been automatically assigned in GitHub to PR creator.

@AntoinePrv AntoinePrv force-pushed the sve-dispatch branch 4 times, most recently from 2925550 to ff8566b Compare April 21, 2026 12:47
@AntoinePrv AntoinePrv marked this pull request as ready for review April 21, 2026 14:01
@pitrou pitrou changed the title GH-47769: [C++] Sve dynamic dispatch GH-47769: [C++] SVE dynamic dispatch Apr 21, 2026
Copy link
Copy Markdown
Member

@pitrou pitrou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this! Here are a number of comments, questions, and suggestions.

Comment thread cpp/cmake_modules/SetupCxxFlags.cmake Outdated
Comment thread cpp/cmake_modules/DefineOptions.cmake
Comment thread cpp/cmake_modules/DefineOptions.cmake
Comment thread cpp/cmake_modules/SetupCxxFlags.cmake
Comment thread cpp/src/arrow/util/bpacking.cc
@@ -17,8 +17,10 @@

#if defined(ARROW_HAVE_NEON)
# define UNPACK_PLATFORM unpack_neon
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just include bpacking_simd_internal.h and reuse the UNPACK_ARCH128 macro?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, though I thought it best that the macro is #undef at the end of the header (making it useless here).
We can make it more explicit (ARROW_BPACKING_UNPACK_ARCH128) an not undefining it.

Comment on lines 27 to +31
#if defined(ARROW_HAVE_NEON)
# define UNPACK_ARCH128 unpack_neon
#elif defined(ARROW_HAVE_SSE4_2)
# define UNPACK_ARCH128 unpack_sse4_2
#endif
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relying on ARROW_HAVE_NEON etc. is why we need the "128 alt" case, right?

Perhaps we can also depend on which target the file is being compiled for.
For example we could have:

macro(append_runtime_sve128_src SRCS SRC)
  if(ARROW_HAVE_RUNTIME_SVE128)
    list(APPEND ${SRCS} ${SRC})
    set_source_files_properties(${SRC}
                                PROPERTIES COMPILE_OPTIONS "${ARROW_SVE128_FLAGS}"
                                           COMPILE_DEFINITIONS
                                           "ARROW_COMPILING_FOR_SVE128")
  endif()
endmacro()

and then:

#if defined(ARROW_COMPILING_FOR_SVE128)
#  define UNPACK_ARCH128 unpack_sve128
#elif defined(ARROW_HAVE_NEON)
#  define UNPACK_ARCH128 unpack_neon
#elif defined(ARROW_HAVE_SSE4_2)
#  define UNPACK_ARCH128 unpack_sse4_2
#endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is we need the file compiled twice in ARM (Neon + sve128).
I think it is not possible directly in CMake. The solution is copy to build tree then compile each with different flags.
Is a CMake-only solution satisfactory?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is we need the file compiled twice in ARM (Neon + sve128).
I think it is not possible directly in CMake.

The easy workaround is to have the same .h file included in two different stub .cc files.

For example have bpacking_simd128_internal.h included by both bpacking_neon.cc and bpacking_sve128.cc.

Comment thread cpp/src/arrow/util/bpacking_test.cc
Comment thread cpp/src/arrow/util/cpu_info.h
Comment thread cpp/src/arrow/util/dispatch_internal.h
@github-actions github-actions Bot added awaiting committer review Awaiting committer review and removed awaiting review Awaiting review labels Apr 21, 2026
@AntoinePrv
Copy link
Copy Markdown
Contributor Author

@pitrou I definitely agree with the duplication of the different files, it's pretty tedious.
I think it will too large this PR, but we should definitely think of something, including providing some CMake utilities in xsimd.

@pitrou pitrou added the CI: Extra: C++ Run extra C++ CI label Apr 22, 2026
@pitrou
Copy link
Copy Markdown
Member

pitrou commented Apr 22, 2026

Something isn't quite right on ARM64 Ubuntu and ARM64 macOS. -march=armv8-a+sve is added to the default compiler flags even though we have ARROW_SIMD_LEVEL=NEON.

Comment thread cpp/cmake_modules/SetupCxxFlags.cmake Outdated
Comment thread cpp/cmake_modules/SetupCxxFlags.cmake
return dispatch.func(in, out, opts);
#endif
auto constexpr kImplementations = UnpackDynamicFunction<Uint>::implementations();
if constexpr (kImplementations.size() == 1) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this condition actually useful? I guess it's a shortcut, but it's not obvious that it applies to common cases (x86 or ARM with default SIMD options).

At worse, this could be added generically to DynamicDispatch instead. But I doubt it's worth it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is worth it to avoid additional #ifdef, for instance on Macos there is only neon and no SVE (no need to dyn dispatch).
Previously we'd exclude the Neon version from the dynamic dispatch and go #ifdef ARROW_HAVE_NEON then go straight to Neon implementation.

At worse, this could be added generically to DynamicDispatch instead. But I doubt it's worth it.

Actually done in GH-49840 so either way here (we'd need to adapt the PR that is not merged first).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually done in GH-49840 so either way here

That PR might prove difficult to adapt for all the lousy compilers we have to support, so I'd rather focus on this one first :)

Comment thread cpp/src/arrow/util/bpacking_benchmark.cc Outdated
@pitrou pitrou added the CI: Extra: C++ Run extra C++ CI label Apr 23, 2026
Comment thread cpp/cmake_modules/SetupCxxFlags.cmake Outdated
->ArgsProduct(kBitWidthsNumValues64);
#endif

#if defined(ARROW_HAVE_RUNTIME_SVE128)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's an easy way to reduce the duplication we're doing for each runtime SIMD level?

For example if we could write something like:

BENCHMARK_SIMD_UNPACK(Bool, bool, SVE128, Sve128, sve128);

and it would expand to:

BENCHMARK_CAPTURE(BM_UnpackBool, Sve128Unaligned, false, &bpacking::unpack_sve128<bool>,
                  !CpuInfo::GetInstance()->IsSupported(CpuInfo::SVE128),
                  "Sve128 not available")
    ->ArgsProduct(kBitWidthsNumValues<bool>);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean with a macro?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

Comment thread cpp/src/arrow/util/bpacking_test.cc Outdated
@pitrou
Copy link
Copy Markdown
Member

pitrou commented Apr 23, 2026

@AntoinePrv Is it possible to run some ARM benchmarks and paste the results somewhere once you're satisfied with the PR?

AntoinePrv and others added 2 commits April 23, 2026 15:55
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
@github-actions github-actions Bot removed the CI: Extra: C++ Run extra C++ CI label Apr 23, 2026
@AntoinePrv
Copy link
Copy Markdown
Contributor Author

@pitrou here are some benchmarks in the [0, 500] num of integer to unpack that Arrow is operating over.

As before, we can sometimes be much penalized by small sizes.
Surprisingly (annoyingly?) there are some bit width numbers (e.g. 3, 5, 6, 7 on SVE256) where the world is upside down: scalar does best, then Neon, then SVE (in the [0, 500] range, it does not hold for larger input buffers).

@pitrou
Copy link
Copy Markdown
Member

pitrou commented Apr 23, 2026

Surprisingly (annoyingly?) there are some bit width numbers (e.g. 3, 5, 6, 7 on SVE256) where the world is upside down: scalar does best, then Neon, then SVE (in the [0, 500] range, it does not hold for larger input buffers).

Perhaps because of larger vectors and a slow epilogue?

@pitrou
Copy link
Copy Markdown
Member

pitrou commented Apr 23, 2026

But it's impressive that SVE128 is always significantly better than NEON. That's rather good news, given that most SVE implementations have 128-bit vectors. @cyb70289

@pitrou
Copy link
Copy Markdown
Member

pitrou commented Apr 23, 2026

@ursabot please benchmark

@rok
Copy link
Copy Markdown
Member

rok commented Apr 23, 2026

Benchmark runs are scheduled for commit cbf526f. Watch https://buildkite.com/apache-arrow and https://conbench.arrow-dev.org for updates. A comment will be posted here when the runs are complete.

@pitrou
Copy link
Copy Markdown
Member

pitrou commented Apr 23, 2026

Silly me, I started the continuous benchmarking suite but our ARM platform there (arm64-t4g-2xlarge) uses Graviton 2 CPUs which don't support SVE.

@conbench-apache-arrow
Copy link
Copy Markdown

Thanks for your patience. Conbench analyzed the 2 benchmarking runs that have been run so far on PR commit cbf526f.

There were 14 benchmark results indicating a performance regression:

The full Conbench report has more details.

@cyb70289
Copy link
Copy Markdown
Contributor

But it's impressive that SVE128 is always significantly better than NEON. That's rather good news, given that most SVE implementations have 128-bit vectors. @cyb70289

Interesting. It should not happen if both using equivalent simd operations.
I tested one case BM_UnpackBool/{Neon,Sve128}Unaligned/1/32 on an Neoverse N2 server, SVE shows double performance than Neon. But from profile result, looks Neon code does not inline frequently called functions like load_val_as and introduces high overhead.

Benchmark

BM_UnpackBool/NeonUnaligned/1/32              11.1 ns         11.0 ns     63329847 items_per_second=2.89667G/s
BM_UnpackBool/Sve128Unaligned/1/32            7.02 ns         7.02 ns     99743767 items_per_second=4.55851G/s

Neon hotspot shows load_val_as is not inlined

+   45.20%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::unpack_width<1, arrow::internal::bpacking::KernelNeon, bool>(unsigned char const
+   31.03%  arrow-bpacking-  libarrow.so.2500.0.0      [.] xsimd::batch<unsigned char, xsimd::neon64> arrow::internal::bpacking::load_val_as<unsigned int, xsimd::neon64>(u
+    7.08%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::MediumKernel<arrow::internal::bpacking::KernelTraits<bool, 1, xsimd::neon64>, ar
+    6.38%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::MediumKernel<arrow::internal::bpacking::KernelTraits<bool, 1, xsimd::neon64>, ar
+    2.63%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::unpack_neon<bool>(unsigned char const*, bool*, arrow::internal::UnpackOptions co
+    1.95%  arrow-bpacking-  arrow-bpacking-benchmark  [.] arrow::internal::(anonymous namespace)::BM_UnpackBool(benchmark::State&, bool, void (*)(unsigned char const*, bo
+    1.82%  arrow-bpacking-  libarrow.so.2500.0.0      [.] xsimd::batch<unsigned char, xsimd::neon64> arrow::internal::bpacking::load_val_as<unsigned int, xsimd::neon64>(u
+    1.37%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::unpack_width<1, arrow::internal::bpacking::KernelNeon, bool>(unsigned char const

No such issue in sve128 code path

+   89.89%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::unpack_width<1, arrow::int◆
+    4.18%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::unpack_sve128<bool>(unsign▒
+    3.11%  arrow-bpacking-  arrow-bpacking-benchmark  [.] arrow::internal::(anonymous namespace)::BM_UnpackBool(benc▒
+    1.47%  arrow-bpacking-  libarrow.so.2500.0.0      [.] void arrow::internal::bpacking::unpack_width<1, arrow::int▒

@AntoinePrv
Copy link
Copy Markdown
Contributor Author

That is interesting, I was also investigating a std::memcpy not inlined in the epilogue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants