mirror of
https://github.com/boostorg/histogram.git
synced 2025-05-11 13:14:06 +00:00
thread-safe storage
- new thread-safe accumulators for arithmetic types - storages have a new flag `has_threading_support` to indicate support for parallel cell access - histogram automatically synchronises growing axes and storage if necessary
This commit is contained in:
parent
66599ddb7c
commit
1bf9dd9dfd
@ -9,7 +9,7 @@ An [*Accumulator] is a functor which consumes the argument to update some intern
|
||||
* `ts...` is a pack of values
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`a(ts...)` or `++a`]
|
||||
[]
|
||||
@ -39,7 +39,7 @@ An [*Accumulator] is a functor which consumes the argument to update some intern
|
||||
* `a` and `b` are values of type `A`
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Return] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`a += b`]
|
||||
[`A&`]
|
||||
|
@ -14,7 +14,7 @@ An [*Axis] maps input values to indices. It holds state specific to that axis, l
|
||||
* `I` is an alias for [headerref boost/histogram/fwd.hpp `boost::histogram::index_type`]
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`a.size()`]
|
||||
[`I`]
|
||||
@ -45,7 +45,7 @@ An [*Axis] maps input values to indices. It holds state specific to that axis, l
|
||||
* `M` is a metadata type that is [@https://en.cppreference.com/w/cpp/named_req/CopyConstructible CopyConstructible] and [@https://en.cppreference.com/w/cpp/named_req/CopyAssignable CopyAssignable]
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`a.update(v)`]
|
||||
[`std::pair<I, I>`]
|
||||
|
@ -18,7 +18,7 @@ A [*DiscreteAxis] is one of two optional refinements of the [link histogram.conc
|
||||
* `ReAxisIter` is a reverse /RandomAccessIterator/ over the bins of `A`
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`a.value(i)`]
|
||||
[`V`]
|
||||
|
@ -20,7 +20,7 @@ A [*IntervalAxis] is one of two optional refinements of the [link histogram.conc
|
||||
* `ReAxisIter` is a reverse /RandomAccessIterator/ over the bins of `A`
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`a.value(i)`]
|
||||
[`V`]
|
||||
|
@ -9,7 +9,7 @@ A [*Storage] handles memory for the bin counters and provides a uniform vector-l
|
||||
* `i` and `n` are values of type `std::size_t`
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`S::value_type`]
|
||||
[]
|
||||
@ -45,6 +45,14 @@ A [*Storage] handles memory for the bin counters and provides a uniform vector-l
|
||||
Returns an STL-compliant iterator type which dereferences to `S::const_reference`.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`S::has_threading_support`]
|
||||
[bool]
|
||||
[
|
||||
Static constexpr member. True, if storage supports parallel read-write access to all cells.
|
||||
False, if such parallel access would either cause data corruption or require synchronization so that effectively only one cell can be accessed at a time, making cell-access single-threaded.
|
||||
]
|
||||
]
|
||||
[
|
||||
[`s.size()`]
|
||||
[`std::size_t`]
|
||||
@ -125,7 +133,7 @@ A [*Storage] handles memory for the bin counters and provides a uniform vector-l
|
||||
* `ar` is a value of an archive with Boost.Serialization semantics
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`s *= x`]
|
||||
[`S&`]
|
||||
|
@ -12,7 +12,7 @@ A [*Transform] implements a monotonic mapping between two real-valued domains, e
|
||||
* `y` is a value of type `Y`
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`t.forward(x)`]
|
||||
[`Y`]
|
||||
@ -43,7 +43,7 @@ A [*Transform] implements a monotonic mapping between two real-valued domains, e
|
||||
* `ar` is a value of an archive with Boost.Serialization semantics
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Return type] [Semantics, Pre/Post-conditions]]
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`ar & t`]
|
||||
[]
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <boost/histogram/accumulators/mean.hpp>
|
||||
#include <boost/histogram/accumulators/sum.hpp>
|
||||
#include <boost/histogram/accumulators/thread_safe.hpp>
|
||||
#include <boost/histogram/accumulators/weighted_mean.hpp>
|
||||
#include <boost/histogram/accumulators/weighted_sum.hpp>
|
||||
#include <boost/histogram/algorithm/project.hpp>
|
||||
|
@ -15,34 +15,41 @@
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace accumulators {
|
||||
template <typename CharT, typename Traits, typename W>
|
||||
template <class CharT, class Traits, class W>
|
||||
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
|
||||
const sum<W>& x) {
|
||||
os << "sum(" << x.large() << " + " << x.small() << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename CharT, typename Traits, typename W>
|
||||
template <class CharT, class Traits, class W>
|
||||
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
|
||||
const weighted_sum<W>& x) {
|
||||
os << "weighted_sum(" << x.value() << ", " << x.variance() << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename CharT, typename Traits, typename W>
|
||||
template <class CharT, class Traits, class W>
|
||||
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
|
||||
const mean<W>& x) {
|
||||
os << "mean(" << x.count() << ", " << x.value() << ", " << x.variance() << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <typename CharT, typename Traits, typename W>
|
||||
template <class CharT, class Traits, class W>
|
||||
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
|
||||
const weighted_mean<W>& x) {
|
||||
os << "weighted_mean(" << x.sum_of_weights() << ", " << x.value() << ", "
|
||||
<< x.variance() << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
template <class CharT, class Traits, class T>
|
||||
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
|
||||
const thread_safe<T>& x) {
|
||||
os << x.load();
|
||||
return os;
|
||||
}
|
||||
} // namespace accumulators
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
45
include/boost/histogram/accumulators/thread_safe.hpp
Normal file
45
include/boost/histogram/accumulators/thread_safe.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2019 Hans Dembinski
|
||||
//
|
||||
// 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)
|
||||
|
||||
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_THREAD_SAFE_HPP
|
||||
#define BOOST_HISTOGRAM_ACCUMULATORS_THREAD_SAFE_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <boost/mp11/utility.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace accumulators {
|
||||
|
||||
template <class T>
|
||||
class thread_safe : public std::atomic<T> {
|
||||
public:
|
||||
using super_t = std::atomic<T>;
|
||||
|
||||
thread_safe() noexcept : super_t(T()) {}
|
||||
// non-atomic copy and assign is allowed, because storage is locked in this case
|
||||
thread_safe(const thread_safe& o) noexcept : super_t(o.load()) {}
|
||||
thread_safe& operator=(const thread_safe& o) noexcept {
|
||||
super_t::store(o.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
thread_safe(T arg) : super_t(arg) {}
|
||||
thread_safe& operator=(T arg) {
|
||||
super_t::store(arg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T operator+=(T arg) { return super_t::fetch_add(arg, std::memory_order_relaxed); }
|
||||
T operator++() { return operator+=(static_cast<T>(1)); }
|
||||
};
|
||||
|
||||
} // namespace accumulators
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
||||
#endif
|
@ -52,7 +52,7 @@ class variable : public iterator_mixin<variable<Value, MetaData, Options, Alloca
|
||||
using vec_type = std::vector<Value, allocator_type>;
|
||||
|
||||
public:
|
||||
explicit variable(allocator_type alloc = {}) : vec_meta_(std::move(alloc)) {}
|
||||
explicit variable(allocator_type alloc = {}) : vec_meta_(vec_type{alloc}) {}
|
||||
|
||||
/** Construct from iterator range of bin edges.
|
||||
*
|
||||
|
@ -20,40 +20,62 @@ class compressed_pair_impl;
|
||||
template <typename T1, typename T2>
|
||||
class compressed_pair_impl<T1, T2, true> : protected T2 {
|
||||
public:
|
||||
template <typename U1, typename U2>
|
||||
compressed_pair_impl(U1&& u1, U2&& u2)
|
||||
: T2(std::forward<U2>(u2)), first_(std::forward<U1>(u1)) {}
|
||||
template <typename U1>
|
||||
compressed_pair_impl(U1&& u1) : first_(std::forward<U1>(u1)) {}
|
||||
compressed_pair_impl() = default;
|
||||
using first_type = T1;
|
||||
using second_type = T2;
|
||||
|
||||
T1& first() { return first_; }
|
||||
T2& second() { return static_cast<T2&>(*this); }
|
||||
const T1& first() const { return first_; }
|
||||
const T2& second() const { return static_cast<const T2&>(*this); }
|
||||
compressed_pair_impl() = default;
|
||||
compressed_pair_impl(compressed_pair_impl&&) = default;
|
||||
compressed_pair_impl(const compressed_pair_impl&) = default;
|
||||
compressed_pair_impl& operator=(compressed_pair_impl&&) = default;
|
||||
compressed_pair_impl& operator=(const compressed_pair_impl&) = default;
|
||||
|
||||
compressed_pair_impl(const first_type& x, const second_type& y)
|
||||
: T2(std::move(y)), first_(std::move(x)) {}
|
||||
|
||||
compressed_pair_impl(first_type&& x, second_type&& y)
|
||||
: T2(std::move(y)), first_(std::move(x)) {}
|
||||
|
||||
compressed_pair_impl(first_type&& x) : first_(std::move(x)) {}
|
||||
compressed_pair_impl(const first_type& x) : first_(x) {}
|
||||
|
||||
first_type& first() { return first_; }
|
||||
second_type& second() { return static_cast<second_type&>(*this); }
|
||||
const first_type& first() const { return first_; }
|
||||
const second_type& second() const { return static_cast<const second_type&>(*this); }
|
||||
|
||||
private:
|
||||
T1 first_;
|
||||
first_type first_;
|
||||
};
|
||||
|
||||
template <typename T1, typename T2>
|
||||
class compressed_pair_impl<T1, T2, false> {
|
||||
public:
|
||||
template <typename U1, typename U2>
|
||||
compressed_pair_impl(U1&& u1, U2&& u2)
|
||||
: first_(std::forward<U1>(u1)), second_(std::forward<U2>(u2)) {}
|
||||
template <typename U1>
|
||||
compressed_pair_impl(U1&& u1) : first_(std::forward<U1>(u1)) {}
|
||||
compressed_pair_impl() = default;
|
||||
using first_type = T1;
|
||||
using second_type = T2;
|
||||
|
||||
T1& first() { return first_; }
|
||||
T2& second() { return second_; }
|
||||
const T1& first() const { return first_; }
|
||||
const T2& second() const { return second_; }
|
||||
compressed_pair_impl() = default;
|
||||
compressed_pair_impl(compressed_pair_impl&&) = default;
|
||||
compressed_pair_impl(const compressed_pair_impl&) = default;
|
||||
compressed_pair_impl& operator=(compressed_pair_impl&&) = default;
|
||||
compressed_pair_impl& operator=(const compressed_pair_impl&) = default;
|
||||
|
||||
compressed_pair_impl(first_type&& x, second_type&& y)
|
||||
: first_(std::move(x)), second_(std::move(y)) {}
|
||||
|
||||
compressed_pair_impl(const first_type& x, const second_type& y)
|
||||
: first_(x), second_(y) {}
|
||||
|
||||
compressed_pair_impl(first_type&& x) : first_(std::move(x)) {}
|
||||
compressed_pair_impl(const first_type& x) : first_(x) {}
|
||||
|
||||
first_type& first() { return first_; }
|
||||
second_type& second() { return second_; }
|
||||
const first_type& first() const { return first_; }
|
||||
const second_type& second() const { return second_; }
|
||||
|
||||
private:
|
||||
T1 first_;
|
||||
T2 second_;
|
||||
first_type first_;
|
||||
second_type second_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <boost/mp11/list.hpp>
|
||||
#include <boost/mp11/tuple.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
@ -82,18 +83,20 @@ void linearize_value(optional_index& o, const axis::variant<Ts...>& a, const Val
|
||||
|
||||
// for growing axis
|
||||
template <class Axis, class Value>
|
||||
void linearize_value(optional_index& o, axis::index_type& s, Axis& a, const Value& v) {
|
||||
bool linearize_value(optional_index& o, axis::index_type& s, Axis& a, const Value& v) {
|
||||
axis::index_type j;
|
||||
std::tie(j, s) = axis::traits::update(a, v);
|
||||
j += has_underflow<Axis>::value;
|
||||
linearize(o, axis::traits::extent(a), j);
|
||||
return s != 0;
|
||||
}
|
||||
|
||||
// for variant which contains at least one growing axis
|
||||
template <class... Ts, class Value>
|
||||
void linearize_value(optional_index& o, axis::index_type& s, axis::variant<Ts...>& a,
|
||||
bool linearize_value(optional_index& o, axis::index_type& s, axis::variant<Ts...>& a,
|
||||
const Value& v) {
|
||||
axis::visit([&o, &s, &v](auto&& a) { linearize_value(o, s, a, v); }, a);
|
||||
return s != 0;
|
||||
}
|
||||
|
||||
template <class A>
|
||||
@ -105,17 +108,13 @@ void linearize_index(optional_index& out, const A& axis, const axis::index_type
|
||||
linearize(out, n + shift, j + shift);
|
||||
}
|
||||
|
||||
template <class S, class A, class T>
|
||||
void maybe_replace_storage(S& storage, const A& axes, const T& shifts) {
|
||||
bool update_needed = false;
|
||||
auto sit = shifts;
|
||||
for_each_axis(axes, [&](const auto&) { update_needed |= (*sit++ != 0); });
|
||||
if (!update_needed) return;
|
||||
template <class S, class A>
|
||||
void grow_storage(S& storage, const A& axes, const axis::index_type* shifts) {
|
||||
struct item {
|
||||
axis::index_type idx, old_extent;
|
||||
std::size_t new_stride;
|
||||
} data[buffer_size<A>::value];
|
||||
sit = shifts;
|
||||
const auto* sit = shifts;
|
||||
auto dit = data;
|
||||
std::size_t s = 1;
|
||||
for_each_axis(axes, [&](const auto& a) {
|
||||
@ -200,22 +199,28 @@ optional_index args_to_index(std::false_type, S&, const T& axes, const U& args)
|
||||
|
||||
// histogram has growing axes
|
||||
template <unsigned I, unsigned N, class S, class T, class U>
|
||||
optional_index args_to_index(std::true_type, S& storage, T& axes, const U& args) {
|
||||
optional_index args_to_index(std::true_type, S& storage_and_mutex, T& axes,
|
||||
const U& args) {
|
||||
optional_index idx;
|
||||
axis::index_type shifts[buffer_size<T>::value];
|
||||
const auto rank = get_size(axes);
|
||||
std::lock_guard<typename S::second_type> lk{storage_and_mutex.second()};
|
||||
bool update_needed = false;
|
||||
if (rank == 1 && N > 1)
|
||||
linearize_value(idx, shifts[0], axis_get<0>(axes), tuple_slice<I, N>(args));
|
||||
update_needed =
|
||||
linearize_value(idx, shifts[0], axis_get<0>(axes), tuple_slice<I, N>(args));
|
||||
else {
|
||||
if (rank != N)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
std::invalid_argument("number of arguments != histogram rank"));
|
||||
constexpr unsigned M = buffer_size<remove_cvref_t<decltype(axes)>>::value;
|
||||
mp11::mp_for_each<mp11::mp_iota_c<(N < M ? N : M)>>([&](auto J) {
|
||||
linearize_value(idx, shifts[J], axis_get<J>(axes), std::get<(J + I)>(args));
|
||||
update_needed |=
|
||||
linearize_value(idx, shifts[J], axis_get<J>(axes), std::get<(J + I)>(args));
|
||||
});
|
||||
}
|
||||
maybe_replace_storage(storage, axes, shifts);
|
||||
|
||||
if (update_needed) grow_storage(storage_and_mutex.first(), axes, shifts);
|
||||
return idx;
|
||||
}
|
||||
|
||||
@ -289,19 +294,20 @@ void fill_storage(IW, IS, T&& t, U&& args) {
|
||||
}
|
||||
|
||||
template <class S, class A, class... Us>
|
||||
auto fill(S& storage, A& axes, const std::tuple<Us...>& args) {
|
||||
auto fill(S& storage_and_mutex, A& axes, const std::tuple<Us...>& args) {
|
||||
constexpr auto iws = weight_sample_indices<Us...>();
|
||||
constexpr unsigned n = sizeof...(Us) - (iws.first > -1) - (iws.second > -1);
|
||||
constexpr unsigned i = (iws.first == 0 || iws.second == 0)
|
||||
? (iws.first == 1 || iws.second == 1 ? 2 : 1)
|
||||
: 0;
|
||||
optional_index idx = args_to_index<i, n>(has_growing_axis<A>(), storage, axes, args);
|
||||
optional_index idx =
|
||||
args_to_index<i, n>(has_growing_axis<A>(), storage_and_mutex, axes, args);
|
||||
if (idx) {
|
||||
fill_storage(mp11::mp_int<iws.first>(), mp11::mp_int<iws.second>(), storage[*idx],
|
||||
args);
|
||||
return storage.begin() + *idx;
|
||||
fill_storage(mp11::mp_int<iws.first>(), mp11::mp_int<iws.second>(),
|
||||
storage_and_mutex.first()[*idx], args);
|
||||
return storage_and_mutex.first().begin() + *idx;
|
||||
}
|
||||
return storage.end();
|
||||
return storage_and_mutex.first().end();
|
||||
}
|
||||
|
||||
template <typename A, typename... Us>
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Hans Dembinski
|
||||
// Copyright 2015-2019 Hans Dembinski
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt
|
||||
@ -82,6 +82,14 @@ template <class Value = double>
|
||||
class mean;
|
||||
template <class Value = double>
|
||||
class weighted_mean;
|
||||
|
||||
template <class T>
|
||||
class thread_safe;
|
||||
|
||||
template <class T>
|
||||
struct is_thread_safe : std::false_type {};
|
||||
template <class T>
|
||||
struct is_thread_safe<thread_safe<T>> : std::true_type {};
|
||||
} // namespace accumulators
|
||||
|
||||
struct unsafe_access;
|
||||
@ -94,20 +102,20 @@ class storage_adaptor;
|
||||
|
||||
#endif // BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
|
||||
/// Vector-like storage for fast zero-overhead access to cells
|
||||
/// Vector-like storage for fast zero-overhead access to cells.
|
||||
template <class T, class A = std::allocator<T>>
|
||||
using dense_storage = storage_adaptor<std::vector<T, A>>;
|
||||
|
||||
/// Default storage, optimized for unweighted histograms
|
||||
using default_storage = unlimited_storage<>;
|
||||
|
||||
/// Dense storage which tracks sums of weights and a variance estimate
|
||||
/// Dense storage which tracks sums of weights and a variance estimate.
|
||||
using weight_storage = dense_storage<accumulators::weighted_sum<>>;
|
||||
|
||||
/// Dense storage which tracks means of samples in each cell
|
||||
/// Dense storage which tracks means of samples in each cell.
|
||||
using profile_storage = dense_storage<accumulators::mean<>>;
|
||||
|
||||
/// Dense storage which tracks means of weighted samples in each cell
|
||||
/// Dense storage which tracks means of weighted samples in each cell.
|
||||
using weighted_profile_storage = dense_storage<accumulators::weighted_mean<>>;
|
||||
|
||||
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
|
@ -9,11 +9,13 @@
|
||||
|
||||
#include <boost/histogram/detail/axes.hpp>
|
||||
#include <boost/histogram/detail/common_type.hpp>
|
||||
#include <boost/histogram/detail/compressed_pair.hpp>
|
||||
#include <boost/histogram/detail/linearize.hpp>
|
||||
#include <boost/histogram/detail/meta.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/mp11/list.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
@ -56,38 +58,56 @@ public:
|
||||
using const_iterator = typename storage_type::const_iterator;
|
||||
|
||||
histogram() = default;
|
||||
histogram(const histogram& rhs) = default;
|
||||
histogram(histogram&& rhs) = default;
|
||||
histogram& operator=(const histogram& rhs) = default;
|
||||
histogram& operator=(histogram&& rhs) = default;
|
||||
|
||||
template <class A, class S>
|
||||
explicit histogram(histogram<A, S>&& rhs) : storage_(std::move(rhs.storage_)) {
|
||||
detail::axes_assign(axes_, rhs.axes_);
|
||||
histogram(const histogram& rhs)
|
||||
: axes_(rhs.axes_), storage_and_mutex_(rhs.storage_and_mutex_.first()) {}
|
||||
histogram(histogram&& rhs)
|
||||
: axes_(std::move(rhs.axes_))
|
||||
, storage_and_mutex_(std::move(rhs.storage_and_mutex_.first())) {}
|
||||
histogram& operator=(histogram&& rhs) {
|
||||
if (this != &rhs) {
|
||||
axes_ = std::move(rhs.axes_);
|
||||
storage_and_mutex_.first() = std::move(rhs.storage_and_mutex_.first());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
histogram& operator=(const histogram& rhs) {
|
||||
if (this != &rhs) {
|
||||
axes_ = rhs.axes_;
|
||||
storage_and_mutex_.first() = rhs.storage_and_mutex_.first();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class A, class S>
|
||||
explicit histogram(const histogram<A, S>& rhs) : storage_(rhs.storage_) {
|
||||
detail::axes_assign(axes_, rhs.axes_);
|
||||
explicit histogram(histogram<A, S>&& rhs)
|
||||
: storage_and_mutex_(std::move(unsafe_access::storage(rhs))) {
|
||||
detail::axes_assign(axes_, std::move(unsafe_access::axes(rhs)));
|
||||
}
|
||||
|
||||
template <class A, class S>
|
||||
explicit histogram(const histogram<A, S>& rhs)
|
||||
: storage_and_mutex_(unsafe_access::storage(rhs)) {
|
||||
detail::axes_assign(axes_, unsafe_access::axes(rhs));
|
||||
}
|
||||
|
||||
template <class A, class S>
|
||||
histogram& operator=(histogram<A, S>&& rhs) {
|
||||
detail::axes_assign(axes_, std::move(rhs.axes_));
|
||||
storage_ = std::move(rhs.storage_);
|
||||
detail::axes_assign(axes_, std::move(unsafe_access::axes(rhs)));
|
||||
storage_and_mutex_.first() = std::move(unsafe_access::storage(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class A, class S>
|
||||
histogram& operator=(const histogram<A, S>& rhs) {
|
||||
detail::axes_assign(axes_, rhs.axes_);
|
||||
storage_ = rhs.storage_;
|
||||
detail::axes_assign(axes_, unsafe_access::axes(rhs));
|
||||
storage_and_mutex_.first() = unsafe_access::storage(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class A, class S>
|
||||
histogram(A&& a, S&& s) : axes_(std::forward<A>(a)), storage_(std::forward<S>(s)) {
|
||||
storage_.reset(detail::bincount(axes_));
|
||||
histogram(A&& a, S&& s)
|
||||
: axes_(std::forward<A>(a)), storage_and_mutex_(std::forward<S>(s)) {
|
||||
storage_and_mutex_.first().reset(detail::bincount(axes_));
|
||||
}
|
||||
|
||||
template <class A, class = detail::requires_axes<A>>
|
||||
@ -99,10 +119,10 @@ public:
|
||||
}
|
||||
|
||||
/// Total number of bins (including underflow/overflow).
|
||||
std::size_t size() const noexcept { return storage_.size(); }
|
||||
std::size_t size() const noexcept { return storage_and_mutex_.first().size(); }
|
||||
|
||||
/// Reset all bins to default initialized values.
|
||||
void reset() { storage_.reset(storage_.size()); }
|
||||
void reset() { storage_and_mutex_.first().reset(size()); }
|
||||
|
||||
/// Get N-th axis using a compile-time number.
|
||||
/// This version is more efficient than the one accepting a run-time number.
|
||||
@ -113,7 +133,7 @@ public:
|
||||
}
|
||||
|
||||
/// Get N-th axis with run-time number.
|
||||
/// Use version that accepts a compile-time number, if possible.
|
||||
/// Use the version that accepts a compile-time number, if possible.
|
||||
decltype(auto) axis(unsigned i) const {
|
||||
detail::axis_index_is_valid(axes_, i);
|
||||
return detail::axis_get(axes_, i);
|
||||
@ -160,7 +180,7 @@ public:
|
||||
/// Fill histogram with values, an optional weight, and/or a sample from a `std::tuple`.
|
||||
template <class... Ts>
|
||||
auto operator()(const std::tuple<Ts...>& t) {
|
||||
return detail::fill(storage_, axes_, t);
|
||||
return detail::fill(storage_and_mutex_, axes_, t);
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices.
|
||||
@ -188,7 +208,7 @@ public:
|
||||
decltype(auto) at(const std::tuple<Ts...>& t) {
|
||||
const auto idx = detail::at(axes_, t);
|
||||
if (!idx) BOOST_THROW_EXCEPTION(std::out_of_range("indices out of bounds"));
|
||||
return storage_[*idx];
|
||||
return storage_and_mutex_.first()[*idx];
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices stored in `std::tuple` (read-only).
|
||||
@ -197,7 +217,7 @@ public:
|
||||
decltype(auto) at(const std::tuple<Ts...>& t) const {
|
||||
const auto idx = detail::at(axes_, t);
|
||||
if (!idx) BOOST_THROW_EXCEPTION(std::out_of_range("indices out of bounds"));
|
||||
return storage_[*idx];
|
||||
return storage_and_mutex_.first()[*idx];
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices stored in iterable.
|
||||
@ -206,7 +226,7 @@ public:
|
||||
decltype(auto) at(const Iterable& c) {
|
||||
const auto idx = detail::at(axes_, c);
|
||||
if (!idx) BOOST_THROW_EXCEPTION(std::out_of_range("indices out of bounds"));
|
||||
return storage_[*idx];
|
||||
return storage_and_mutex_.first()[*idx];
|
||||
}
|
||||
|
||||
/// Access cell value at integral indices stored in iterable (read-only).
|
||||
@ -215,7 +235,7 @@ public:
|
||||
decltype(auto) at(const Iterable& c) const {
|
||||
const auto idx = detail::at(axes_, c);
|
||||
if (!idx) BOOST_THROW_EXCEPTION(std::out_of_range("indices out of bounds"));
|
||||
return storage_[*idx];
|
||||
return storage_and_mutex_.first()[*idx];
|
||||
}
|
||||
|
||||
/// Access value at index (number for rank = 1, else `std::tuple` or iterable).
|
||||
@ -233,7 +253,8 @@ public:
|
||||
/// Equality operator, tests equality for all axes and the storage.
|
||||
template <class A, class S>
|
||||
bool operator==(const histogram<A, S>& rhs) const noexcept {
|
||||
return detail::axes_equal(axes_, rhs.axes_) && storage_ == rhs.storage_;
|
||||
return detail::axes_equal(axes_, unsafe_access::axes(rhs)) &&
|
||||
storage_and_mutex_.first() == unsafe_access::storage(rhs);
|
||||
}
|
||||
|
||||
/// Negation of the equality operator.
|
||||
@ -248,10 +269,11 @@ public:
|
||||
static_assert(detail::has_operator_radd<value_type,
|
||||
typename histogram<A, S>::value_type>::value,
|
||||
"cell value does not support adding value of right-hand-side");
|
||||
if (!detail::axes_equal(axes_, rhs.axes_))
|
||||
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
|
||||
auto rit = rhs.storage_.begin();
|
||||
std::for_each(storage_.begin(), storage_.end(), [&rit](auto&& x) { x += *rit++; });
|
||||
auto rit = unsafe_access::storage(rhs).begin();
|
||||
auto& s = storage_and_mutex_.first();
|
||||
std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x += *rit++; });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -261,10 +283,11 @@ public:
|
||||
static_assert(detail::has_operator_rsub<value_type,
|
||||
typename histogram<A, S>::value_type>::value,
|
||||
"cell value does not support subtracting value of right-hand-side");
|
||||
if (!detail::axes_equal(axes_, rhs.axes_))
|
||||
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
|
||||
auto rit = rhs.storage_.begin();
|
||||
std::for_each(storage_.begin(), storage_.end(), [&rit](auto&& x) { x -= *rit++; });
|
||||
auto rit = unsafe_access::storage(rhs).begin();
|
||||
auto& s = storage_and_mutex_.first();
|
||||
std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x -= *rit++; });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -274,10 +297,11 @@ public:
|
||||
static_assert(detail::has_operator_rmul<value_type,
|
||||
typename histogram<A, S>::value_type>::value,
|
||||
"cell value does not support multiplying by value of right-hand-side");
|
||||
if (!detail::axes_equal(axes_, rhs.axes_))
|
||||
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
|
||||
auto rit = rhs.storage_.begin();
|
||||
std::for_each(storage_.begin(), storage_.end(), [&rit](auto&& x) { x *= *rit++; });
|
||||
auto rit = unsafe_access::storage(rhs).begin();
|
||||
auto& s = storage_and_mutex_.first();
|
||||
std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x *= *rit++; });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -287,10 +311,11 @@ public:
|
||||
static_assert(detail::has_operator_rdiv<value_type,
|
||||
typename histogram<A, S>::value_type>::value,
|
||||
"cell value does not support dividing by value of right-hand-side");
|
||||
if (!detail::axes_equal(axes_, rhs.axes_))
|
||||
if (!detail::axes_equal(axes_, unsafe_access::axes(rhs)))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("axes of histograms differ"));
|
||||
auto rit = rhs.storage_.begin();
|
||||
std::for_each(storage_.begin(), storage_.end(), [&rit](auto&& x) { x /= *rit++; });
|
||||
auto rit = unsafe_access::storage(rhs).begin();
|
||||
auto& s = storage_and_mutex_.first();
|
||||
std::for_each(s.begin(), s.end(), [&rit](auto&& x) { x /= *rit++; });
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -304,7 +329,7 @@ public:
|
||||
[](storage_type& s, auto x) {
|
||||
for (auto&& si : s) si *= x;
|
||||
},
|
||||
storage_, x);
|
||||
storage_and_mutex_.first(), x);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -316,29 +341,37 @@ public:
|
||||
}
|
||||
|
||||
/// Return value iterator to the beginning of the histogram.
|
||||
iterator begin() noexcept { return storage_.begin(); }
|
||||
iterator begin() noexcept { return storage_and_mutex_.first().begin(); }
|
||||
|
||||
/// Return value iterator to the end in the histogram.
|
||||
iterator end() noexcept { return storage_.end(); }
|
||||
iterator end() noexcept { return storage_and_mutex_.first().end(); }
|
||||
|
||||
/// Return value iterator to the beginning of the histogram (read-only).
|
||||
const_iterator begin() const noexcept { return storage_.begin(); }
|
||||
const_iterator begin() const noexcept { return storage_and_mutex_.first().begin(); }
|
||||
|
||||
/// Return value iterator to the end in the histogram (read-only).
|
||||
const_iterator end() const noexcept { return storage_.end(); }
|
||||
const_iterator end() const noexcept { return storage_and_mutex_.first().end(); }
|
||||
|
||||
/// Return value iterator to the beginning of the histogram (read-only).
|
||||
const_iterator cbegin() const noexcept { return storage_.begin(); }
|
||||
const_iterator cbegin() const noexcept { return begin(); }
|
||||
|
||||
/// Return value iterator to the end in the histogram (read-only).
|
||||
const_iterator cend() const noexcept { return storage_.end(); }
|
||||
const_iterator cend() const noexcept { return end(); }
|
||||
|
||||
private:
|
||||
axes_type axes_;
|
||||
storage_type storage_;
|
||||
|
||||
template <class A, class S>
|
||||
friend class histogram;
|
||||
struct noop_mutex {
|
||||
bool try_lock() noexcept { return true; }
|
||||
void lock() noexcept {}
|
||||
void unlock() noexcept {}
|
||||
};
|
||||
using mutex_type = mp11::mp_if_c<(storage_type::has_threading_support &&
|
||||
detail::has_growing_axis<axes_type>::value),
|
||||
std::mutex, noop_mutex>;
|
||||
|
||||
detail::compressed_pair<storage_type, mutex_type> storage_and_mutex_;
|
||||
|
||||
friend struct unsafe_access;
|
||||
};
|
||||
|
||||
|
@ -171,8 +171,9 @@ void serialize(Archive& ar, mp_int<Allocator>& x, unsigned /* version */) {
|
||||
} // namespace detail
|
||||
|
||||
template <class Archive, class T>
|
||||
void serialize(Archive& ar, storage_adaptor<T>& s, unsigned /* version */) {
|
||||
ar& serialization::make_nvp("impl", static_cast<detail::storage_adaptor_impl<T>&>(s));
|
||||
void serialize(Archive& ar, storage_adaptor<T>& x, unsigned /* version */) {
|
||||
using impl_t = typename storage_adaptor<T>::base_type;
|
||||
ar& serialization::make_nvp("impl", static_cast<impl_t&>(x));
|
||||
}
|
||||
|
||||
template <class A>
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2018 Hans Dembinski
|
||||
// Copyright 2018-2019 Hans Dembinski
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0.
|
||||
// (See accompanying file LICENSE_1_0.txt
|
||||
@ -8,13 +8,13 @@
|
||||
#define BOOST_HISTOGRAM_STORAGE_ADAPTOR_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/histogram/detail/cat.hpp>
|
||||
#include <boost/histogram/detail/meta.hpp>
|
||||
#include <boost/histogram/fwd.hpp>
|
||||
#include <boost/iterator/iterator_adaptor.hpp>
|
||||
#include <boost/mp11/utility.hpp>
|
||||
#include <boost/throw_exception.hpp>
|
||||
#include <iosfwd>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
@ -24,10 +24,18 @@ namespace detail {
|
||||
|
||||
template <class T>
|
||||
struct vector_impl : T {
|
||||
vector_impl() = default;
|
||||
static constexpr bool has_threading_support =
|
||||
accumulators::is_thread_safe<typename T::value_type>::value;
|
||||
|
||||
vector_impl() = default;
|
||||
vector_impl(const vector_impl& t) : T(t) {}
|
||||
vector_impl& operator=(const vector_impl& t) {
|
||||
T::operator=(t);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit vector_impl(const T& t) : T(t) {}
|
||||
explicit vector_impl(typename T::allocator_type a) : T(a) {}
|
||||
explicit vector_impl(const T& t) : T(t) {}
|
||||
|
||||
template <class U, class = requires_iterable<U>>
|
||||
explicit vector_impl(const U& u) {
|
||||
@ -53,7 +61,16 @@ struct vector_impl : T {
|
||||
|
||||
template <class T>
|
||||
struct array_impl : T {
|
||||
static constexpr bool has_threading_support =
|
||||
accumulators::is_thread_safe<typename T::value_type>::value;
|
||||
|
||||
array_impl() = default;
|
||||
array_impl(const array_impl& t) : T(t), size_(t.size_) {}
|
||||
array_impl& operator=(const array_impl& t) {
|
||||
T::operator=(t);
|
||||
size_ = t.size_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit array_impl(const T& t) : T(t) {}
|
||||
template <class U, class = requires_iterable<U>>
|
||||
@ -98,10 +115,17 @@ struct map_impl : T {
|
||||
using value_type = typename T::mapped_type;
|
||||
using const_reference = const value_type&;
|
||||
|
||||
static constexpr bool has_threading_support = false;
|
||||
static_assert(
|
||||
!accumulators::is_thread_safe<value_type>::value,
|
||||
"std::map and std::unordered_map do not support thread-safe element access. "
|
||||
"If you have a map with thread-safe element access, please file an issue and"
|
||||
"support will be added.");
|
||||
|
||||
struct reference {
|
||||
reference(map_impl* m, std::size_t i) noexcept : map(m), idx(i) {}
|
||||
reference(const reference&) noexcept = default;
|
||||
reference operator=(reference o) {
|
||||
reference& operator=(const reference& o) {
|
||||
if (this != &o) operator=(static_cast<const_reference>(o));
|
||||
return *this;
|
||||
}
|
||||
@ -110,15 +134,14 @@ struct map_impl : T {
|
||||
return static_cast<const map_impl*>(map)->operator[](idx);
|
||||
}
|
||||
|
||||
template <class U, class = requires_convertible<U, value_type>>
|
||||
reference& operator=(const U& u) {
|
||||
reference& operator=(const_reference u) {
|
||||
auto it = map->find(idx);
|
||||
if (u == value_type()) {
|
||||
if (it != static_cast<T*>(map)->end()) map->erase(it);
|
||||
if (u == value_type{}) {
|
||||
if (it != static_cast<T*>(map)->end()) { map->erase(it); }
|
||||
} else {
|
||||
if (it != static_cast<T*>(map)->end())
|
||||
if (it != static_cast<T*>(map)->end()) {
|
||||
it->second = u;
|
||||
else {
|
||||
} else {
|
||||
map->emplace(idx, u);
|
||||
}
|
||||
}
|
||||
@ -127,29 +150,31 @@ struct map_impl : T {
|
||||
|
||||
template <class U, class V = value_type,
|
||||
class = std::enable_if_t<has_operator_radd<V, U>::value>>
|
||||
reference operator+=(const U& u) {
|
||||
reference& operator+=(const U& u) {
|
||||
auto it = map->find(idx);
|
||||
if (it != static_cast<T*>(map)->end())
|
||||
if (it != static_cast<T*>(map)->end()) {
|
||||
it->second += u;
|
||||
else
|
||||
} else {
|
||||
map->emplace(idx, u);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U, class V = value_type,
|
||||
class = std::enable_if_t<has_operator_rsub<V, U>::value>>
|
||||
reference operator-=(const U& u) {
|
||||
reference& operator-=(const U& u) {
|
||||
auto it = map->find(idx);
|
||||
if (it != static_cast<T*>(map)->end())
|
||||
if (it != static_cast<T*>(map)->end()) {
|
||||
it->second -= u;
|
||||
else
|
||||
} else {
|
||||
map->emplace(idx, -u);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U, class V = value_type,
|
||||
class = std::enable_if_t<has_operator_rmul<V, U>::value>>
|
||||
reference operator*=(const U& u) {
|
||||
reference& operator*=(const U& u) {
|
||||
auto it = map->find(idx);
|
||||
if (it != static_cast<T*>(map)->end()) it->second *= u;
|
||||
return *this;
|
||||
@ -157,12 +182,13 @@ struct map_impl : T {
|
||||
|
||||
template <class U, class V = value_type,
|
||||
class = std::enable_if_t<has_operator_rdiv<V, U>::value>>
|
||||
reference operator/=(const U& u) {
|
||||
reference& operator/=(const U& u) {
|
||||
auto it = map->find(idx);
|
||||
if (it != static_cast<T*>(map)->end())
|
||||
if (it != static_cast<T*>(map)->end()) {
|
||||
it->second /= u;
|
||||
else if (!(value_type{} / u == value_type{}))
|
||||
} else if (!(value_type{} / u == value_type{})) {
|
||||
map->emplace(idx, value_type{} / u);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -170,21 +196,41 @@ struct map_impl : T {
|
||||
class = std::enable_if_t<has_operator_preincrement<V>::value>>
|
||||
reference operator++() {
|
||||
auto it = map->find(idx);
|
||||
if (it != static_cast<T*>(map)->end())
|
||||
if (it != static_cast<T*>(map)->end()) {
|
||||
++it->second;
|
||||
else
|
||||
map->emplace(idx, 1);
|
||||
} else {
|
||||
value_type tmp{};
|
||||
++tmp;
|
||||
map->emplace(idx, tmp);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class V = value_type,
|
||||
class = std::enable_if_t<has_operator_preincrement<V>::value>>
|
||||
value_type operator++(int) {
|
||||
const value_type tmp = operator const_reference();
|
||||
const value_type tmp = *this;
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
template <class U, class = std::enable_if_t<has_operator_equal<value_type, U>::value>>
|
||||
bool operator==(const U& rhs) const {
|
||||
return operator const_reference() == rhs;
|
||||
}
|
||||
|
||||
template <class U, class = std::enable_if_t<has_operator_equal<value_type, U>::value>>
|
||||
bool operator!=(const U& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
template <typename CharT, typename Traits>
|
||||
friend std::basic_ostream<CharT, Traits>& operator<<(
|
||||
std::basic_ostream<CharT, Traits>& os, reference x) {
|
||||
os << static_cast<const_reference>(x);
|
||||
return os;
|
||||
}
|
||||
|
||||
template <class... Ts>
|
||||
decltype(auto) operator()(Ts&&... args) {
|
||||
return map->operator[](idx)(std::forward<Ts>(args)...);
|
||||
@ -216,6 +262,12 @@ struct map_impl : T {
|
||||
using const_iterator = iterator_t<const value_type, const_reference, const map_impl*>;
|
||||
|
||||
map_impl() = default;
|
||||
map_impl(const map_impl& t) : T(t), size_(t.size_) {}
|
||||
map_impl& operator=(const map_impl& t) {
|
||||
T::operator=(t);
|
||||
size_ = t.size_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit map_impl(const T& t) : T(t) {}
|
||||
explicit map_impl(typename T::allocator_type a) : T(a) {}
|
||||
@ -263,26 +315,49 @@ struct map_impl : T {
|
||||
std::size_t size_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template <class T>
|
||||
struct ERROR_type_passed_to_storage_adaptor_not_recognized;
|
||||
|
||||
template <typename T>
|
||||
using storage_adaptor_impl = mp11::mp_if<
|
||||
// clang-format off
|
||||
template <class T>
|
||||
using storage_adaptor_impl =
|
||||
mp11::mp_cond<
|
||||
is_vector_like<T>, vector_impl<T>,
|
||||
mp11::mp_if<is_array_like<T>, array_impl<T>,
|
||||
mp11::mp_if<is_map_like<T>, map_impl<T>,
|
||||
ERROR_type_passed_to_storage_adaptor_not_recognized<T>>>>;
|
||||
|
||||
is_array_like<T>, array_impl<T>,
|
||||
is_map_like<T>, map_impl<T>,
|
||||
std::true_type, ERROR_type_passed_to_storage_adaptor_not_recognized<T>
|
||||
>;
|
||||
// clang-format on
|
||||
} // namespace detail
|
||||
|
||||
/// Turns any vector-like array-like, and map-like container into a storage type.
|
||||
template <typename T>
|
||||
/// Turns any vector-like, array-like, and map-like container into a storage type.
|
||||
template <class T>
|
||||
class storage_adaptor : public detail::storage_adaptor_impl<T> {
|
||||
public:
|
||||
using base_type = detail::storage_adaptor_impl<T>;
|
||||
|
||||
public:
|
||||
using base_type::base_type;
|
||||
using base_type::operator=;
|
||||
// standard default, copy, move, assign
|
||||
storage_adaptor() = default;
|
||||
storage_adaptor(storage_adaptor&&) = default;
|
||||
storage_adaptor(const storage_adaptor&) = default;
|
||||
storage_adaptor& operator=(storage_adaptor&&) = default;
|
||||
storage_adaptor& operator=(const storage_adaptor&) = default;
|
||||
|
||||
// allow move from adapted object
|
||||
storage_adaptor(T&& t) : base_type(std::move(t)) {}
|
||||
storage_adaptor& operator=(T&& t) {
|
||||
base_type::operator=(std::move(t));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// conversion ctor and assign
|
||||
template <class U>
|
||||
storage_adaptor(const U& u) : base_type(u) {}
|
||||
template <class U>
|
||||
storage_adaptor& operator=(const U& u) {
|
||||
base_type::operator=(u);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class U, class = detail::requires_iterable<U>>
|
||||
bool operator==(const U& u) const {
|
||||
|
@ -421,6 +421,8 @@ class unlimited_storage {
|
||||
"unlimited_storage requires allocator with trivial pointer type");
|
||||
|
||||
public:
|
||||
static constexpr bool has_threading_support = false;
|
||||
|
||||
using allocator_type = Allocator;
|
||||
using value_type = double;
|
||||
using mp_int = detail::mp_int<
|
||||
|
@ -58,13 +58,13 @@ struct unsafe_access {
|
||||
*/
|
||||
template <class Histogram>
|
||||
static auto& storage(Histogram& hist) {
|
||||
return hist.storage_;
|
||||
return hist.storage_and_mutex_.first();
|
||||
}
|
||||
|
||||
/// @copydoc storage()
|
||||
template <class Histogram>
|
||||
static const auto& storage(const Histogram& hist) {
|
||||
return hist.storage_;
|
||||
return hist.storage_and_mutex_.first();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -54,11 +54,13 @@ test-suite minimal :
|
||||
[ run histogram_mixed_test.cpp ]
|
||||
[ run histogram_operators_test.cpp ]
|
||||
[ run histogram_test.cpp ]
|
||||
[ run histogram_threaded_test.cpp ]
|
||||
[ run indexed_test.cpp ]
|
||||
[ run internal_accumulators_test.cpp ]
|
||||
[ run linearize_test.cpp ]
|
||||
[ run meta_test.cpp ]
|
||||
[ run storage_adaptor_test.cpp ]
|
||||
[ run storage_adaptor_threaded_test.cpp ]
|
||||
[ run unlimited_storage_test.cpp ]
|
||||
[ run utility_test.cpp ]
|
||||
;
|
||||
|
@ -96,8 +96,8 @@ void run_tests() {
|
||||
h(0, 0);
|
||||
auto h2 = decltype(h)(h);
|
||||
BOOST_TEST_EQ(h2, h);
|
||||
auto h3 = histogram<std::tuple<axis::integer<>, axis::integer<>>,
|
||||
storage_adaptor<std::vector<double>>>(h);
|
||||
auto h3 =
|
||||
histogram<std::tuple<axis::integer<>, axis::integer<>>, dense_storage<double>>(h);
|
||||
BOOST_TEST_EQ(h3, h);
|
||||
}
|
||||
|
||||
@ -109,8 +109,8 @@ void run_tests() {
|
||||
BOOST_TEST_NE(h, h2);
|
||||
h2 = h;
|
||||
BOOST_TEST_EQ(h, h2);
|
||||
auto h3 = histogram<std::tuple<axis::integer<>, axis::integer<>>,
|
||||
storage_adaptor<std::vector<double>>>();
|
||||
auto h3 =
|
||||
histogram<std::tuple<axis::integer<>, axis::integer<>>, dense_storage<double>>();
|
||||
h3 = h;
|
||||
BOOST_TEST_EQ(h, h3);
|
||||
}
|
||||
|
71
test/histogram_threaded_test.cpp
Normal file
71
test/histogram_threaded_test.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
// Copyright 2019 Hans Dembinski
|
||||
//
|
||||
// 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 <boost/core/lightweight_test.hpp>
|
||||
#include <boost/histogram.hpp>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include "utility_histogram.hpp"
|
||||
|
||||
using namespace boost::histogram;
|
||||
|
||||
constexpr auto n_fill = 400000;
|
||||
|
||||
template <class Tag, class A1, class A2, class X, class Y>
|
||||
void fill_test(const A1& a1, const A2& a2, const X& x, const Y& y) {
|
||||
auto h1 = make_s(Tag{}, dense_storage<int>(), a1, a2);
|
||||
for (unsigned i = 0; i != n_fill; ++i) h1(x[i], y[i]);
|
||||
auto h2 = make_s(Tag{}, dense_storage<accumulators::thread_safe<int>>(), a1, a2);
|
||||
auto run = [&h2, &x, &y](int k) {
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(10));
|
||||
constexpr auto shift = n_fill / 4;
|
||||
auto xit = x.cbegin() + k * shift;
|
||||
auto yit = y.cbegin() + k * shift;
|
||||
for (unsigned i = 0; i < shift; ++i) h2(*xit++, *yit++);
|
||||
};
|
||||
|
||||
std::thread t1([&] { run(0); });
|
||||
std::thread t2([&] { run(1); });
|
||||
std::thread t3([&] { run(2); });
|
||||
std::thread t4([&] { run(3); });
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
t4.join();
|
||||
|
||||
BOOST_TEST_EQ(algorithm::sum(h1), n_fill);
|
||||
BOOST_TEST_EQ(algorithm::sum(h2), n_fill);
|
||||
BOOST_TEST_EQ(h1, h2);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void tests() {
|
||||
std::mt19937 gen(1);
|
||||
std::uniform_real_distribution<> rd(-5, 5);
|
||||
std::uniform_int_distribution<> id(-5, 5);
|
||||
std::vector<double> vd(n_fill);
|
||||
std::generate(vd.begin(), vd.end(), [&] { return rd(gen); });
|
||||
std::vector<int> vi(n_fill);
|
||||
std::generate(vi.begin(), vi.end(), [&] { return id(gen); });
|
||||
|
||||
using r = axis::regular<>;
|
||||
using i = axis::integer<>;
|
||||
using rg = axis::regular<double, use_default, use_default, axis::option::growth_t>;
|
||||
using ig = axis::integer<int, use_default, axis::option::growth_t>;
|
||||
fill_test<T>(r(2, 0, 1), i{0, 1}, vd, vi);
|
||||
fill_test<T>(rg(2, 0, 1), i{0, 1}, vd, vi);
|
||||
fill_test<T>(r(2, 0, 1), ig{0, 1}, vd, vi);
|
||||
fill_test<T>(rg(2, 0, 1), ig{0, 1}, vd, vi);
|
||||
}
|
||||
|
||||
int main() {
|
||||
tests<static_tag>();
|
||||
tests<dynamic_tag>();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
@ -8,12 +8,14 @@
|
||||
#include <boost/histogram/accumulators/mean.hpp>
|
||||
#include <boost/histogram/accumulators/ostream.hpp>
|
||||
#include <boost/histogram/accumulators/sum.hpp>
|
||||
#include <boost/histogram/accumulators/thread_safe.hpp>
|
||||
#include <boost/histogram/accumulators/weighted_mean.hpp>
|
||||
#include <boost/histogram/accumulators/weighted_sum.hpp>
|
||||
#include <sstream>
|
||||
#include "is_close.hpp"
|
||||
|
||||
using namespace boost::histogram;
|
||||
using namespace std::literals;
|
||||
|
||||
template <class T>
|
||||
auto str(const T& t) {
|
||||
@ -26,7 +28,7 @@ int main() {
|
||||
{
|
||||
using w_t = accumulators::weighted_sum<double>;
|
||||
w_t w;
|
||||
BOOST_TEST_EQ(str(w), std::string("weighted_sum(0, 0)"));
|
||||
BOOST_TEST_EQ(str(w), "weighted_sum(0, 0)"s);
|
||||
|
||||
BOOST_TEST_EQ(w, w_t(0));
|
||||
BOOST_TEST_NE(w, w_t(1));
|
||||
@ -77,7 +79,7 @@ int main() {
|
||||
BOOST_TEST_EQ(a.value(), 10);
|
||||
BOOST_TEST_EQ(a.variance(), 30);
|
||||
|
||||
BOOST_TEST_EQ(str(a), std::string("mean(4, 10, 30)"));
|
||||
BOOST_TEST_EQ(str(a), "mean(4, 10, 30)"s);
|
||||
|
||||
m_t b;
|
||||
b(1e8 + 4);
|
||||
@ -110,7 +112,7 @@ int main() {
|
||||
BOOST_TEST_EQ(a.value(), 2);
|
||||
BOOST_TEST_IS_CLOSE(a.variance(), 0.8, 1e-3);
|
||||
|
||||
BOOST_TEST_EQ(str(a), std::string("weighted_mean(2, 2, 0.8)"));
|
||||
BOOST_TEST_EQ(str(a), "weighted_mean(2, 2, 0.8)"s);
|
||||
|
||||
auto b = a;
|
||||
b += a; // same as feeding all samples twice
|
||||
@ -132,13 +134,13 @@ int main() {
|
||||
++sum;
|
||||
BOOST_TEST_EQ(sum.large(), 1);
|
||||
BOOST_TEST_EQ(sum.small(), 0);
|
||||
BOOST_TEST_EQ(str(sum), std::string("sum(1 + 0)"));
|
||||
BOOST_TEST_EQ(str(sum), "sum(1 + 0)"s);
|
||||
sum += 1e100;
|
||||
BOOST_TEST_EQ(str(sum), std::string("sum(1e+100 + 1)"));
|
||||
BOOST_TEST_EQ(str(sum), "sum(1e+100 + 1)"s);
|
||||
++sum;
|
||||
BOOST_TEST_EQ(str(sum), std::string("sum(1e+100 + 2)"));
|
||||
BOOST_TEST_EQ(str(sum), "sum(1e+100 + 2)"s);
|
||||
sum += -1e100;
|
||||
BOOST_TEST_EQ(str(sum), std::string("sum(0 + 2)"));
|
||||
BOOST_TEST_EQ(str(sum), "sum(0 + 2)"s);
|
||||
BOOST_TEST_EQ(sum, 2); // correct answer
|
||||
BOOST_TEST_EQ(sum.large(), 0);
|
||||
BOOST_TEST_EQ(sum.small(), 2);
|
||||
@ -164,5 +166,14 @@ int main() {
|
||||
BOOST_TEST_EQ(w.variance(), 2e200);
|
||||
}
|
||||
|
||||
{
|
||||
accumulators::thread_safe<int> i;
|
||||
++i;
|
||||
i += 1000;
|
||||
|
||||
BOOST_TEST_EQ(i, 1001);
|
||||
BOOST_TEST_EQ(str(i), "1001"s);
|
||||
}
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
@ -14,11 +14,20 @@
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "is_close.hpp"
|
||||
#include "utility_allocator.hpp"
|
||||
|
||||
using namespace boost::histogram;
|
||||
using namespace std::literals;
|
||||
|
||||
template <class T>
|
||||
auto str(const T& t) {
|
||||
std::ostringstream os;
|
||||
os << t;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void tests() {
|
||||
@ -110,6 +119,13 @@ void tests() {
|
||||
BOOST_TEST_EQ(c.size(), 1);
|
||||
BOOST_TEST_EQ(c[0], 1);
|
||||
}
|
||||
|
||||
{
|
||||
storage_adaptor<T> a;
|
||||
a.reset(1);
|
||||
a[0] += 2;
|
||||
BOOST_TEST_EQ(str(a[0]), "2"s);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A, typename B>
|
||||
|
57
test/storage_adaptor_threaded_test.cpp
Normal file
57
test/storage_adaptor_threaded_test.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2019 Hans Dembinski
|
||||
//
|
||||
// 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 <boost/core/lightweight_test.hpp>
|
||||
#include <boost/histogram/accumulators/ostream.hpp>
|
||||
#include <boost/histogram/accumulators/thread_safe.hpp>
|
||||
#include <boost/histogram/storage_adaptor.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using namespace boost::histogram;
|
||||
|
||||
constexpr auto n_fill = 1000000;
|
||||
|
||||
template <class T>
|
||||
void tests() {
|
||||
{
|
||||
storage_adaptor<T> s;
|
||||
s.reset(1);
|
||||
|
||||
auto fill = [&s]() {
|
||||
for (unsigned i = 0; i < n_fill; ++i) {
|
||||
++s[0];
|
||||
s[0] += 1;
|
||||
}
|
||||
};
|
||||
|
||||
std::thread t1(fill);
|
||||
std::thread t2(fill);
|
||||
std::thread t3(fill);
|
||||
std::thread t4(fill);
|
||||
t1.join();
|
||||
t2.join();
|
||||
t3.join();
|
||||
t4.join();
|
||||
|
||||
BOOST_TEST_EQ(s[0], 4 * 2 * n_fill);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
using ts_int = accumulators::thread_safe<int>;
|
||||
tests<std::vector<ts_int>>();
|
||||
tests<std::array<ts_int, 100>>();
|
||||
tests<std::deque<ts_int>>();
|
||||
// stdlib maps are not thread-safe and not supported
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user