From b9127bb1fe3541865a8bd217fef4d1d91967cb93 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 2 Nov 2026 11:14:58 -0500 Subject: [PATCH] perf: vectorize the fill loop when arguments are variants When the fill arguments are variants, index_visitor runs through variant2::visit and is not inlined. The loop then reads its bounds and stride through `this` on every iteration and the vectorizer gives up. Copy the bounds to locals and give the first axis a compile-time stride. Measured with a 1D regular axis and 1M doubles (clang, Apple M5 Pro): variant argument 3.3 -> 0.8 ns/element, same as a plain span. Assisted-by: ClaudeCode:claude-fable-5-1 ClaudeCode:claude-fable-5-1 --- include/boost/histogram/detail/fill_n.hpp | 27 +++++++++++++++++------ 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/include/boost/histogram/detail/fill_n.hpp b/include/boost/histogram/detail/fill_n.hpp index a282a403..991b1c08 100644 --- a/include/boost/histogram/detail/fill_n.hpp +++ b/include/boost/histogram/detail/fill_n.hpp @@ -86,26 +86,39 @@ struct index_visitor { void dispatch_on_value_or_iterable(std::false_type, const T& iterable) const { // T is iterable; fill N values const auto* tp = dtl::data(iterable) + start_; - for (auto it = begin_; it != begin_ + size_; ++it) - linearize_element(IsGrowing{}, it, *tp++); + // When this runs through a variant visit, the members are read through `this` + // and the loop is not vectorized. Locals and a constant stride for the first + // axis keep the loop simple enough for the vectorizer. + const pointer begin = begin_; + const pointer end = begin_ + size_; + if (stride_ == 1) { + for (auto it = begin; it != end; ++it) + linearize_element(IsGrowing{}, it, std::size_t{1}, *tp++); + } else { + const std::size_t stride = stride_; + for (auto it = begin; it != end; ++it) + linearize_element(IsGrowing{}, it, stride, *tp++); + } } template - void linearize_element(std::true_type, pointer it, const T& x) const { + void linearize_element(std::true_type, pointer it, const std::size_t stride, + const T& x) const { // must use this code for all axes if one of them is growing axis::index_type shift; - linearize_growth(*it, shift, stride_, axis_, + linearize_growth(*it, shift, stride, axis_, try_cast(x)); if (shift > 0) { // shift previous indices, because axis zero-point has changed - while (it != begin_) *--it += static_cast(shift) * stride_; + while (it != begin_) *--it += static_cast(shift) * stride; *shift_ += shift; } } template - void linearize_element(std::false_type, pointer it, const T& x) const { + void linearize_element(std::false_type, pointer it, const std::size_t stride, + const T& x) const { // no axis is growing - linearize(*it, stride_, axis_, try_cast(x)); + linearize(*it, stride, axis_, try_cast(x)); } template