Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ add_benchmark(axis_size)
add_benchmark(axis_index)
add_benchmark(histogram_filling)
add_benchmark(histogram_iteration)
add_benchmark(histogram_reduce)
add_benchmark(detail_normal)

find_package(Threads)
Expand Down
112 changes: 112 additions & 0 deletions benchmark/histogram_reduce.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2026 Henry Schreiner
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <benchmark/benchmark.h>
#include <boost/histogram/algorithm/reduce.hpp>
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/make_histogram.hpp>
#include <boost/histogram/storage_adaptor.hpp>
#include <boost/histogram/unlimited_storage.hpp>
#include <boost/histogram/unsafe_access.hpp>
#include <boost/mp11/integral.hpp>
#include <random>
#include <vector>
#include "../test/throw_exception.hpp"

#include <cassert>
struct assert_check {
assert_check() {
assert(false); // don't run with asserts enabled
}
} _;

using namespace boost::histogram;

struct dense {};
struct unlimited {};

template <unsigned I>
using Dim_t = boost::mp11::mp_int<I>;

auto make_storage(dense) { return dense_storage<double>(); }
auto make_storage(unlimited) { return unlimited_storage<>(); }

template <class Tag, int Dim>
auto make_histogram(Tag, boost::mp11::mp_int<Dim>, int n) {
std::vector<axis::regular<>> axes;
for (int d = 0; d < Dim; ++d) axes.emplace_back(n, 0.0, 1.0);
auto h = make_histogram_with(make_storage(Tag{}), std::move(axes));
// cell values do not affect reduce speed, they only must be non-trivial
std::default_random_engine gen(1);
std::uniform_real_distribution<double> dis(0, 10);
for (auto&& v : unsafe_access::storage(h)) v = dis(gen);
return h;
}

template <class Histogram>
void run_reduce(benchmark::State& state, const Histogram& h,
const std::vector<algorithm::reduce_command>& opts) {
for (auto _ : state) {
auto r = algorithm::reduce(h, opts);
benchmark::DoNotOptimize(r);
}
// report throughput in input cells (including flow bins) per second
state.SetItemsProcessed(state.iterations() * static_cast<int64_t>(h.size()));
}

template <class Tag, int Dim>
static void Rebin(benchmark::State& state, Tag, boost::mp11::mp_int<Dim> d) {
auto h = make_histogram(Tag{}, d, static_cast<int>(state.range(0)));
std::vector<algorithm::reduce_command> opts;
for (int i = 0; i < Dim; ++i) opts.push_back(algorithm::rebin(i, 2));
run_reduce(state, h, opts);
}

template <class Tag, int Dim>
static void Shrink(benchmark::State& state, Tag, boost::mp11::mp_int<Dim> d) {
auto h = make_histogram(Tag{}, d, static_cast<int>(state.range(0)));
std::vector<algorithm::reduce_command> opts;
for (int i = 0; i < Dim; ++i) opts.push_back(algorithm::shrink(i, 0.2, 0.8));
run_reduce(state, h, opts);
}

template <class Tag, int Dim>
static void ShrinkAndRebin(benchmark::State& state, Tag, boost::mp11::mp_int<Dim> d) {
auto h = make_histogram(Tag{}, d, static_cast<int>(state.range(0)));
std::vector<algorithm::reduce_command> opts;
for (int i = 0; i < Dim; ++i)
opts.push_back(algorithm::shrink_and_rebin(i, 0.2, 0.8, 2));
run_reduce(state, h, opts);
}

#define BENCH(Type, Tag, Dim) \
BENCHMARK_CAPTURE(Type, (Tag, Dim), Tag{}, Dim_t<Dim>{}) \
->RangeMultiplier(4) \
->Range(4, 256)

BENCH(Rebin, dense, 1);
BENCH(Shrink, dense, 1);
BENCH(ShrinkAndRebin, dense, 1);

BENCH(Rebin, dense, 2);
BENCH(Shrink, dense, 2);
BENCH(ShrinkAndRebin, dense, 2);

BENCH(Rebin, dense, 3);
BENCH(Shrink, dense, 3);
BENCH(ShrinkAndRebin, dense, 3);

BENCH(Rebin, unlimited, 1);
BENCH(Shrink, unlimited, 1);
BENCH(ShrinkAndRebin, unlimited, 1);

BENCH(Rebin, unlimited, 2);
BENCH(Shrink, unlimited, 2);
BENCH(ShrinkAndRebin, unlimited, 2);

BENCH(Rebin, unlimited, 3);
BENCH(Shrink, unlimited, 3);
BENCH(ShrinkAndRebin, unlimited, 3);
11 changes: 6 additions & 5 deletions include/boost/histogram/algorithm/reduce.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ Histogram reduce(const Histogram& hist, const Iterable& options) {
// example [1, 4] with merge = 2 is reduced to [1, 3]
o.end.index -=
(o.end.index - o.begin.index) % static_cast<index_type>(o.merge);
o.reduced_end =
(o.end.index - o.begin.index) / static_cast<index_type>(o.merge);
using A = std::decay_t<decltype(a_in)>;
return A(a_in, o.begin.index, o.end.index, o.merge);
},
Expand All @@ -398,6 +400,7 @@ Histogram reduce(const Histogram& hist, const Iterable& options) {
o.merge = 1;
o.begin.index = 0;
o.end.index = a_in.size();
o.reduced_end = a_in.size();
return a_in;
}
});
Expand All @@ -420,11 +423,9 @@ Histogram reduce(const Histogram& hist, const Iterable& options) {
if (*i >= 0)
*i /= static_cast<index_type>(o->merge);
else
*i = o->end.index;
const auto reduced_axis_end =
(o->end.index - o->begin.index) / static_cast<index_type>(o->merge);
if (*i >= reduced_axis_end) {
*i = reduced_axis_end;
*i = o->reduced_end;
if (*i >= o->reduced_end) {
*i = o->reduced_end;
if (!o->use_overflow_bin) skip = true;
}
}
Expand Down
1 change: 1 addition & 0 deletions include/boost/histogram/detail/reduce_command.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct reduce_command {
unsigned merge = 0; // default value indicates unset option
bool crop = false;
// for internal use by the reduce algorithm
axis::index_type reduced_end = 0; // (end - begin) / merge
bool is_ordered = true;
bool use_underflow_bin = true;
bool use_overflow_bin = true;
Expand Down
Loading