Skip to content

Commit 6cde30a

Browse files
authored
deprecate accumulators::thread_safe, replace with optionally thread-safe accumulators::count (#326)
1 parent 30e1a71 commit 6cde30a

27 files changed

+427
-260
lines changed

.github/workflows/fast.yml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,3 @@ jobs:
3838
run: |
3939
cd build
4040
ctest -C Debug --output-on-failure
41-
42-
gcc5:
43-
runs-on: ubuntu-16.04
44-
steps:
45-
- uses: actions/checkout@v2
46-
- name: cmake
47-
run: |
48-
mkdir build
49-
cd build
50-
CXX=g++-5 cmake ..
51-
- name: ctest
52-
run: |
53-
cd build
54-
ctest -C Debug --output-on-failure

.github/workflows/slow.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ jobs:
8080
run: |
8181
./bootstrap.sh
8282
./b2 headers
83-
- name: Test cxxstd=14
83+
- name: Test cxxstd=14 (warnings ignored)
8484
run: |
8585
cd libs/histogram
86-
../../b2 $B2_OPTS toolset=gcc-5 cxxstd=14 test//all examples
86+
../../b2 -q -j2 toolset=gcc-5 cxxstd=14 test//all examples
8787
8888
gcc10:
8989
runs-on: ubuntu-latest

doc/guide.qbk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ There are two ways to generate a single histogram using several threads.
425425

426426
1. Each thread has its own copy of the histogram. Each copy is independently filled. The copies are then added in the main thread. Use this as the default when you can afford having `N` copies of the histogram in memory for `N` threads, because it allows each thread to work on its thread-local memory and utilise the CPU cache without the need to synchronise memory access. The highest performance gains are obtained in this way.
427427

428-
2. There is only one histogram which is filled concurrently by several threads. This requires using a thread-safe storage that can handle concurrent writes. The library provides the [classref boost::histogram::accumulators::thread_safe] accumulator, which combined with the [classref boost::histogram::dense_storage] provides a thread-safe storage.
428+
2. There is only one histogram which is filled concurrently by several threads. This requires using a thread-safe storage that can handle concurrent writes. The library provides the [classref boost::histogram::accumulators::count] accumulator with a thread-safe option, which combined with the [classref boost::histogram::dense_storage] provides a thread-safe storage.
429429

430430
[note Filling a histogram with growing axes in a multi-threaded environment is safe, but has poor performance since the histogram must be locked on each fill. The locks are required because an axis could grow each time, which changes the number of cells and cell addressing for all other threads. Even without growing axes, there is only a performance gain if the histogram is either very large or when significant time is spend in preparing the value to fill. For small histograms, threads frequently access the same cell, whose state has to be synchronised between the threads. This is slow even with atomic counters and made worse by the effect of false sharing.]
431431

examples/guide_parallel_filling.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ int main() {
2626
Create histogram with container of thread-safe counters for parallel filling in
2727
several threads. Only filling is thread-safe, other guarantees are not given.
2828
*/
29-
auto h = make_histogram_with(dense_storage<accumulators::thread_safe<unsigned>>(),
29+
auto h = make_histogram_with(dense_storage<accumulators::count<unsigned, true>>(),
3030
axis::integer<>(0, 10));
3131

3232
/*

include/boost/histogram/accumulators/count.hpp

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
// (See accompanying file LICENSE_1_0.txt
55
// or copy at http://www.boost.org/LICENSE_1_0.txt)
66

7-
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_NUMBER_HPP
8-
#define BOOST_HISTOGRAM_ACCUMULATORS_NUMBER_HPP
7+
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_COUNT_HPP
8+
#define BOOST_HISTOGRAM_ACCUMULATORS_COUNT_HPP
99

1010
#include <boost/core/nvp.hpp>
11+
#include <boost/histogram/detail/atomic_number.hpp>
1112
#include <boost/histogram/fwd.hpp> // for count<>
1213
#include <type_traits> // for std::common_type
1314

@@ -16,32 +17,44 @@ namespace histogram {
1617
namespace accumulators {
1718

1819
/**
19-
Uses a C++ builtin arithmetic type to accumulate a count.
20+
Wraps a C++ arithmetic type with optionally thread-safe increments and adds.
2021
21-
This wrapper class may be used as a base class by users who want to add custom metadata
22-
to each bin of a histogram. Otherwise, arithmetic types should be used directly as
23-
accumulators in storages for simplicity. In other words, prefer `dense_storage<double>`
24-
over `dense_storage<count<double>>`, both are functionally equivalent.
22+
This adaptor optionally uses atomic operations to make concurrent increments and
23+
additions thread-safe for the stored arithmetic value, which can be integral or
24+
floating point. For small histograms, the performance will still be poor because of
25+
False Sharing, see https://en.wikipedia.org/wiki/False_sharing for details.
2526
26-
When weighted data is accumulated and high precision is required, use
27-
`accumulators::sum` instead. If a local variance estimate for the weight distribution
28-
should be computed as well (generally needed for a detailed statistical analysis), use
29-
`accumulators::weighted_sum`.
27+
Warning: Assignment is not thread-safe in this implementation, so don't assign
28+
concurrently.
29+
30+
This wrapper class can be used as a base class by users to add arbitrary metadata to
31+
each bin of a histogram.
32+
33+
When weighted samples are accumulated and high precision is required, use
34+
`accumulators::sum` instead (at the cost of lower performance). If a local variance
35+
estimate for the weight distribution should be computed as well (generally needed for a
36+
detailed statistical analysis), use `accumulators::weighted_sum`.
37+
38+
@tparam T C++ builtin arithmetic type (integer or floating point).
39+
@tparam ThreadSafe Set to true to make increments and adds thread-safe.
3040
*/
31-
template <class ValueType>
41+
template <class ValueType, bool ThreadSafe>
3242
class count {
43+
using internal_type =
44+
std::conditional_t<ThreadSafe, detail::atomic_number<ValueType>, ValueType>;
45+
3346
public:
3447
using value_type = ValueType;
3548
using const_reference = const value_type&;
3649

37-
count() = default;
50+
count() noexcept = default;
3851

3952
/// Initialize count to value and allow implicit conversion
40-
count(const_reference value) noexcept : value_(value) {}
53+
count(const_reference value) noexcept : value_{value} {}
4154

4255
/// Allow implicit conversion from other count
43-
template <class T>
44-
count(const count<T>& c) noexcept : count(c.value()) {}
56+
template <class T, bool B>
57+
count(const count<T, B>& c) noexcept : count{c.value()} {}
4558

4659
/// Increment count by one
4760
count& operator++() noexcept {
@@ -72,16 +85,20 @@ class count {
7285
bool operator!=(const count& rhs) const noexcept { return !operator==(rhs); }
7386

7487
/// Return count
75-
const_reference value() const noexcept { return value_; }
88+
value_type value() const noexcept { return value_; }
7689

7790
// conversion to value_type must be explicit
7891
explicit operator value_type() const noexcept { return value_; }
7992

8093
template <class Archive>
8194
void serialize(Archive& ar, unsigned /* version */) {
82-
ar& make_nvp("value", value_);
95+
auto v = value();
96+
ar& make_nvp("value", v);
97+
value_ = v;
8398
}
8499

100+
static constexpr bool thread_safe() noexcept { return ThreadSafe; }
101+
85102
// begin: extra operators to make count behave like a regular number
86103

87104
count& operator*=(const count& rhs) noexcept {
@@ -114,10 +131,33 @@ class count {
114131

115132
bool operator>=(const count& rhs) const noexcept { return value_ >= rhs.value_; }
116133

134+
friend bool operator==(const_reference x, const count& rhs) noexcept {
135+
return x == rhs.value_;
136+
}
137+
138+
friend bool operator!=(const_reference x, const count& rhs) noexcept {
139+
return x != rhs.value_;
140+
}
141+
142+
friend bool operator<(const_reference x, const count& rhs) noexcept {
143+
return x < rhs.value_;
144+
}
145+
146+
friend bool operator>(const_reference x, const count& rhs) noexcept {
147+
return x > rhs.value_;
148+
}
149+
150+
friend bool operator<=(const_reference x, const count& rhs) noexcept {
151+
return x <= rhs.value_;
152+
}
153+
friend bool operator>=(const_reference x, const count& rhs) noexcept {
154+
return x >= rhs.value_;
155+
}
156+
117157
// end: extra operators
118158

119159
private:
120-
value_type value_{};
160+
internal_type value_{};
121161
};
122162

123163
} // namespace accumulators
@@ -126,10 +166,10 @@ class count {
126166

127167
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
128168
namespace std {
129-
template <class T, class U>
130-
struct common_type<boost::histogram::accumulators::count<T>,
131-
boost::histogram::accumulators::count<U>> {
132-
using type = boost::histogram::accumulators::count<common_type_t<T, U>>;
169+
template <class T, class U, bool B1, bool B2>
170+
struct common_type<boost::histogram::accumulators::count<T, B1>,
171+
boost::histogram::accumulators::count<U, B2>> {
172+
using type = boost::histogram::accumulators::count<common_type_t<T, U>, (B1 || B2)>;
133173
};
134174
} // namespace std
135175
#endif
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2021 Hans Dembinski
2+
//
3+
// Distributed under the Boost Software License, version 1.0.
4+
// (See accompanying file LICENSE_1_0.txt
5+
// or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
7+
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_IS_THREAD_SAFE_HPP
8+
#define BOOST_HISTOGRAM_ACCUMULATORS_IS_THREAD_SAFE_HPP
9+
10+
#include <boost/histogram/detail/priority.hpp>
11+
#include <boost/histogram/fwd.hpp>
12+
#include <type_traits>
13+
14+
namespace boost {
15+
namespace histogram {
16+
namespace detail {
17+
18+
template <class T>
19+
constexpr bool is_thread_safe_impl(priority<0>) {
20+
return false;
21+
}
22+
23+
template <class T>
24+
constexpr auto is_thread_safe_impl(priority<1>) -> decltype(T::thread_safe()) {
25+
return T::thread_safe();
26+
}
27+
28+
} // namespace detail
29+
30+
namespace accumulators {
31+
32+
template <class T>
33+
struct is_thread_safe
34+
: std::integral_constant<bool,
35+
detail::is_thread_safe_impl<T>(detail::priority<1>{})> {};
36+
37+
} // namespace accumulators
38+
} // namespace histogram
39+
} // namespace boost
40+
41+
#endif

include/boost/histogram/accumulators/ostream.hpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ std::basic_ostream<CharT, Traits>& handle_nonzero_width(
5454

5555
namespace accumulators {
5656

57-
template <class CharT, class Traits, class U>
57+
template <class CharT, class Traits, class U, bool B>
5858
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
59-
const count<U>& x) {
59+
const count<U, B>& x) {
6060
return os << x.value();
6161
}
6262

@@ -92,12 +92,15 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
9292
return detail::handle_nonzero_width(os, x);
9393
}
9494

95+
#include <boost/histogram/detail/ignore_deprecation_warning_begin.hpp>
9596
template <class CharT, class Traits, class T>
9697
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
9798
const thread_safe<T>& x) {
9899
os << static_cast<T>(x);
99100
return os;
100101
}
102+
#include <boost/histogram/detail/ignore_deprecation_warning_end.hpp>
103+
101104
} // namespace accumulators
102105
} // namespace histogram
103106
} // namespace boost

0 commit comments

Comments
 (0)