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:
Hans Dembinski 2019-04-14 22:24:34 +02:00 committed by GitHub
parent 66599ddb7c
commit 1bf9dd9dfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 526 additions and 161 deletions

View File

@ -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&`]

View File

@ -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>`]

View File

@ -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`]

View File

@ -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`]

View File

@ -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&`]

View File

@ -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`]
[]

View File

@ -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>

View File

@ -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

View 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

View File

@ -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.
*

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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;
};

View File

@ -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>

View File

@ -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 {

View File

@ -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<

View File

@ -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();
}
};

View File

@ -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 ]
;

View File

@ -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);
}

View 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();
}

View File

@ -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();
}

View File

@ -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>

View 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();
}