deprecate accumulators::thread_safe, replace with optionally thread-safe accumulators::count (#326)

This commit is contained in:
Hans Dembinski 2021-05-17 22:20:47 +02:00 committed by GitHub
parent 30e1a71571
commit 6cde30a366
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 431 additions and 264 deletions

View File

@ -38,17 +38,3 @@ jobs:
run: |
cd build
ctest -C Debug --output-on-failure
gcc5:
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v2
- name: cmake
run: |
mkdir build
cd build
CXX=g++-5 cmake ..
- name: ctest
run: |
cd build
ctest -C Debug --output-on-failure

View File

@ -80,10 +80,10 @@ jobs:
run: |
./bootstrap.sh
./b2 headers
- name: Test cxxstd=14
- name: Test cxxstd=14 (warnings ignored)
run: |
cd libs/histogram
../../b2 $B2_OPTS toolset=gcc-5 cxxstd=14 test//all examples
../../b2 -q -j2 toolset=gcc-5 cxxstd=14 test//all examples
gcc10:
runs-on: ubuntu-latest

View File

@ -425,7 +425,7 @@ There are two ways to generate a single histogram using several threads.
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.
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.
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.
[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.]

View File

@ -26,7 +26,7 @@ int main() {
Create histogram with container of thread-safe counters for parallel filling in
several threads. Only filling is thread-safe, other guarantees are not given.
*/
auto h = make_histogram_with(dense_storage<accumulators::thread_safe<unsigned>>(),
auto h = make_histogram_with(dense_storage<accumulators::count<unsigned, true>>(),
axis::integer<>(0, 10));
/*

View File

@ -4,10 +4,11 @@
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_NUMBER_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_NUMBER_HPP
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_COUNT_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_COUNT_HPP
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/atomic_number.hpp>
#include <boost/histogram/fwd.hpp> // for count<>
#include <type_traits> // for std::common_type
@ -16,32 +17,44 @@ namespace histogram {
namespace accumulators {
/**
Uses a C++ builtin arithmetic type to accumulate a count.
Wraps a C++ arithmetic type with optionally thread-safe increments and adds.
This wrapper class may be used as a base class by users who want to add custom metadata
to each bin of a histogram. Otherwise, arithmetic types should be used directly as
accumulators in storages for simplicity. In other words, prefer `dense_storage<double>`
over `dense_storage<count<double>>`, both are functionally equivalent.
This adaptor optionally uses atomic operations to make concurrent increments and
additions thread-safe for the stored arithmetic value, which can be integral or
floating point. For small histograms, the performance will still be poor because of
False Sharing, see https://en.wikipedia.org/wiki/False_sharing for details.
When weighted data is accumulated and high precision is required, use
`accumulators::sum` instead. If a local variance estimate for the weight distribution
should be computed as well (generally needed for a detailed statistical analysis), use
`accumulators::weighted_sum`.
Warning: Assignment is not thread-safe in this implementation, so don't assign
concurrently.
This wrapper class can be used as a base class by users to add arbitrary metadata to
each bin of a histogram.
When weighted samples are accumulated and high precision is required, use
`accumulators::sum` instead (at the cost of lower performance). If a local variance
estimate for the weight distribution should be computed as well (generally needed for a
detailed statistical analysis), use `accumulators::weighted_sum`.
@tparam T C++ builtin arithmetic type (integer or floating point).
@tparam ThreadSafe Set to true to make increments and adds thread-safe.
*/
template <class ValueType>
template <class ValueType, bool ThreadSafe>
class count {
using internal_type =
std::conditional_t<ThreadSafe, detail::atomic_number<ValueType>, ValueType>;
public:
using value_type = ValueType;
using const_reference = const value_type&;
count() = default;
count() noexcept = default;
/// Initialize count to value and allow implicit conversion
count(const_reference value) noexcept : value_(value) {}
count(const_reference value) noexcept : value_{value} {}
/// Allow implicit conversion from other count
template <class T>
count(const count<T>& c) noexcept : count(c.value()) {}
template <class T, bool B>
count(const count<T, B>& c) noexcept : count{c.value()} {}
/// Increment count by one
count& operator++() noexcept {
@ -72,16 +85,20 @@ public:
bool operator!=(const count& rhs) const noexcept { return !operator==(rhs); }
/// Return count
const_reference value() const noexcept { return value_; }
value_type value() const noexcept { return value_; }
// conversion to value_type must be explicit
explicit operator value_type() const noexcept { return value_; }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
ar& make_nvp("value", value_);
auto v = value();
ar& make_nvp("value", v);
value_ = v;
}
static constexpr bool thread_safe() noexcept { return ThreadSafe; }
// begin: extra operators to make count behave like a regular number
count& operator*=(const count& rhs) noexcept {
@ -114,10 +131,33 @@ public:
bool operator>=(const count& rhs) const noexcept { return value_ >= rhs.value_; }
friend bool operator==(const_reference x, const count& rhs) noexcept {
return x == rhs.value_;
}
friend bool operator!=(const_reference x, const count& rhs) noexcept {
return x != rhs.value_;
}
friend bool operator<(const_reference x, const count& rhs) noexcept {
return x < rhs.value_;
}
friend bool operator>(const_reference x, const count& rhs) noexcept {
return x > rhs.value_;
}
friend bool operator<=(const_reference x, const count& rhs) noexcept {
return x <= rhs.value_;
}
friend bool operator>=(const_reference x, const count& rhs) noexcept {
return x >= rhs.value_;
}
// end: extra operators
private:
value_type value_{};
internal_type value_{};
};
} // namespace accumulators
@ -126,10 +166,10 @@ private:
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace std {
template <class T, class U>
struct common_type<boost::histogram::accumulators::count<T>,
boost::histogram::accumulators::count<U>> {
using type = boost::histogram::accumulators::count<common_type_t<T, U>>;
template <class T, class U, bool B1, bool B2>
struct common_type<boost::histogram::accumulators::count<T, B1>,
boost::histogram::accumulators::count<U, B2>> {
using type = boost::histogram::accumulators::count<common_type_t<T, U>, (B1 || B2)>;
};
} // namespace std
#endif

View File

@ -0,0 +1,41 @@
// Copyright 2021 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_IS_THREAD_SAFE_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_IS_THREAD_SAFE_HPP
#include <boost/histogram/detail/priority.hpp>
#include <boost/histogram/fwd.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
template <class T>
constexpr bool is_thread_safe_impl(priority<0>) {
return false;
}
template <class T>
constexpr auto is_thread_safe_impl(priority<1>) -> decltype(T::thread_safe()) {
return T::thread_safe();
}
} // namespace detail
namespace accumulators {
template <class T>
struct is_thread_safe
: std::integral_constant<bool,
detail::is_thread_safe_impl<T>(detail::priority<1>{})> {};
} // namespace accumulators
} // namespace histogram
} // namespace boost
#endif

View File

@ -54,9 +54,9 @@ std::basic_ostream<CharT, Traits>& handle_nonzero_width(
namespace accumulators {
template <class CharT, class Traits, class U>
template <class CharT, class Traits, class U, bool B>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const count<U>& x) {
const count<U, B>& x) {
return os << x.value();
}
@ -92,12 +92,15 @@ std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>&
return detail::handle_nonzero_width(os, x);
}
#include <boost/histogram/detail/ignore_deprecation_warning_begin.hpp>
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 << static_cast<T>(x);
return os;
}
#include <boost/histogram/detail/ignore_deprecation_warning_end.hpp>
} // namespace accumulators
} // namespace histogram
} // namespace boost

View File

@ -9,15 +9,18 @@
#include <atomic>
#include <boost/core/nvp.hpp>
#include <boost/histogram/detail/priority.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/histogram/fwd.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace accumulators {
/** Thread-safe adaptor for integral and floating point numbers.
// cannot use new mechanism with accumulators::thread_safe
template <class T>
struct is_thread_safe<thread_safe<T>> : std::true_type {};
/** Thread-safe adaptor for builtin integral numbers.
This adaptor uses atomic operations to make concurrent increments and additions safe for
the stored value.
@ -27,130 +30,49 @@ namespace accumulators {
instruction. On exotic platforms the size of the adapted number may be larger and/or the
type may have different alignment, which means it cannot be tightly packed into arrays.
This implementation uses a workaround to support atomic operations on floating point
numbers in C++14. Compiling with C++20 may increase performance for
operations on floating point numbers. The implementation automatically uses the best
implementation that is available.
@tparam T type to adapt; must be arithmetic (integer or floating point).
@tparam T type to adapt, must be an integral type.
*/
template <class T>
class thread_safe {
static_assert(std::is_arithmetic<T>(), "");
class [[deprecated("use count<T, true> instead; "
"thread_safe<T> will be removed in boost-1.79")]] thread_safe
: public std::atomic<T> {
public:
using value_type = T;
using const_reference = const T&;
thread_safe() noexcept : value_{static_cast<value_type>(0)} {}
using super_t = std::atomic<T>;
thread_safe() noexcept : super_t(static_cast<T>(0)) {}
// non-atomic copy and assign is allowed, because storage is locked in this case
thread_safe(const thread_safe& o) noexcept : thread_safe{o.value()} {}
thread_safe(const thread_safe& o) noexcept : super_t(o.load()) {}
thread_safe& operator=(const thread_safe& o) noexcept {
value_.store(o.value());
super_t::store(o.load());
return *this;
}
/// Allow implicit conversion from value.
thread_safe(const_reference value) noexcept : value_{value} {}
/// Allow assignment from value.
thread_safe& operator=(const_reference o) noexcept {
value_.store(o);
thread_safe(value_type arg) : super_t(arg) {}
thread_safe& operator=(value_type arg) {
super_t::store(arg);
return *this;
}
/// Allow implicit conversion from other thread_safe.
template <class U>
thread_safe(const thread_safe<U>& o) noexcept : value_{o.value()} {}
/// Allow assignment from other thread_safe.
template <class U>
thread_safe& operator=(const thread_safe<U>& o) noexcept {
value_.store(o.value());
thread_safe& operator+=(const thread_safe& arg) {
operator+=(arg.load());
return *this;
}
thread_safe& operator+=(value_type arg) {
super_t::fetch_add(arg, std::memory_order_relaxed);
return *this;
}
/// Increment value by one.
thread_safe& operator++() {
increment_impl(detail::priority<1>{});
operator+=(static_cast<value_type>(1));
return *this;
}
/// Increment value by argument.
thread_safe& operator+=(const_reference x) {
add_impl(detail::priority<1>{}, x);
return *this;
}
/// Add another thread_safe.
thread_safe& operator+=(const thread_safe& o) {
add_impl(detail::priority<1>{}, o.value());
return *this;
}
/// Scale by value
thread_safe& operator*=(const_reference value) noexcept {
value_ *= value;
return *this;
}
bool operator==(const thread_safe& rhs) const noexcept { return value_ == rhs.value_; }
bool operator!=(const thread_safe& rhs) const noexcept { return !operator==(rhs); }
/// Return value.
value_type value() const noexcept { return value_.load(); }
// conversion to value_type must be explicit
explicit operator value_type() const noexcept { return value_.load(); }
template <class Archive>
void serialize(Archive& ar, unsigned /* version */) {
auto value = value_.load();
void serialize(Archive & ar, unsigned /* version */) {
auto value = super_t::load();
ar& make_nvp("value", value);
value_.store(value);
super_t::store(value);
}
private:
template <class U = value_type>
auto increment_impl(detail::priority<1>) -> decltype(++std::declval<std::atomic<U>>()) {
return ++value_;
}
template <class U = value_type>
auto add_impl(detail::priority<1>, const_reference x)
-> decltype(std::declval<std::atomic<U>>().fetch_add(x,
std::memory_order_relaxed)) {
return value_.fetch_add(x, std::memory_order_relaxed);
}
// workaround for floating point numbers in C++14, obsolete in C++20
template <class U = value_type>
void increment_impl(detail::priority<0>) {
operator+=(static_cast<value_type>(1));
}
// workaround for floating point numbers in C++14, obsolete in C++20
template <class U = value_type>
void add_impl(detail::priority<0>, const_reference x) {
value_type expected = value_.load();
// if another tread changed expected value, compare_exchange returns false
// and updates expected; we then loop and try to update again;
// see https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
while (!value_.compare_exchange_weak(expected, expected + x))
;
}
friend bool operator==(const_reference x, const thread_safe& rhs) noexcept {
return rhs.operator==(x);
}
friend bool operator!=(const_reference x, const thread_safe& rhs) noexcept {
return rhs.operator!=(x);
}
std::atomic<T> value_;
};
} // namespace accumulators

View File

@ -0,0 +1,80 @@
// Copyright 2021 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_DETAIL_ATOMIC_NUMBER_HPP
#define BOOST_HISTOGRAM_DETAIL_ATOMIC_NUMBER_HPP
#include <atomic>
#include <boost/histogram/detail/priority.hpp>
#include <type_traits>
namespace boost {
namespace histogram {
namespace detail {
// copyable arithmetic type with thread-safe operator++ and operator+=
template <class T>
struct atomic_number : std::atomic<T> {
static_assert(std::is_arithmetic<T>(), "");
using base_t = std::atomic<T>;
using std::atomic<T>::atomic;
atomic_number() noexcept = default;
atomic_number(const atomic_number& o) noexcept : std::atomic<T>{o.load()} {}
atomic_number& operator=(const atomic_number& o) noexcept {
this->store(o.load());
return *this;
}
// not defined for float
atomic_number& operator++() noexcept {
increment_impl(static_cast<base_t&>(*this), priority<1>{});
return *this;
}
// operator is not defined for floating point before C++20
atomic_number& operator+=(const T& x) noexcept {
add_impl(static_cast<base_t&>(*this), x, priority<1>{});
return *this;
}
private:
// for integral types
template <class U = T>
auto increment_impl(std::atomic<U>& a, priority<1>) noexcept -> decltype(++a) {
return ++a;
}
// fallback implementation for floating point types
template <class U = T>
void increment_impl(std::atomic<U>&, priority<0>) noexcept {
this->operator+=(static_cast<U>(1));
}
// always available for integral types, in C++20 also available for float
template <class U = T>
static auto add_impl(std::atomic<U>& a, const U& x, priority<1>) noexcept
-> decltype(a += x) {
return a += x;
}
// pre-C++20 fallback implementation for floating point
template <class U = T>
static void add_impl(std::atomic<U>& a, const U& x, priority<0>) noexcept {
U expected = a.load();
// if another tread changed `expected` in the meantime, compare_exchange returns
// false and updates `expected`; we then loop and try to update again;
// see https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
while (!a.compare_exchange_weak(expected, expected + x))
;
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

View File

@ -0,0 +1,8 @@
#if (defined(__GNUC__) || defined(__clang__))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#elif defined(_MSC_VER)
#pragma warning(push) // preserve warning settings
#pragma warning(disable : 4996) // disable depricated localtime/gmtime warning on vc8
#endif

View File

@ -0,0 +1,6 @@
#if (defined(__GNUC__) || defined(__clang__))
#pragma GCC diagnostic pop
#elif defined(_MSC_VER)
#pragma warning(pop)
#endif

View File

@ -7,13 +7,15 @@
#ifndef BOOST_HISTOGRAM_DETAIL_TERM_INFO_HPP
#define BOOST_HISTOGRAM_DETAIL_TERM_INFO_HPP
#include <algorithm>
#if defined __has_include
#if __has_include(<sys/ioctl.h>) && __has_include(<unistd.h>)
#include <sys/ioctl.h>
#include <unistd.h>
#endif
#endif
#include <boost/config/workaround.hpp>
#include <boost/config.hpp>
#include <cstdlib>
#include <cstring>
@ -25,33 +27,35 @@ namespace term_info {
class env_t {
public:
env_t(const char* key) {
#if BOOST_WORKAROUND(BOOST_MSVC, >= 0) // msvc complains about using std::getenv
_dupenv_s(&data, &size, key);
#if defined(BOOST_MSVC) // msvc complains about using std::getenv
_dupenv_s(&data_, &size_, key);
#else
data = std::getenv(key);
if (data) size = std::strlen(data);
data_ = std::getenv(key);
if (data_) size_ = std::strlen(data_);
#endif
}
~env_t() {
#if BOOST_WORKAROUND(BOOST_MSVC, >= 0)
std::free(data);
#if defined(BOOST_MSVC)
std::free(data_);
#endif
}
bool contains(const char* s) {
const std::size_t n = std::strlen(s);
if (size < n) return false;
return std::strstr(data, s);
if (size_ < n) return false;
return std::strstr(data_, s);
}
operator bool() { return size > 0; }
operator bool() { return size_ > 0; }
explicit operator int() { return size ? std::atoi(data) : 0; }
explicit operator int() { return size_ ? std::atoi(data_) : 0; }
const char* data() const { return data_; }
private:
char* data;
std::size_t size = 0;
char* data_;
std::size_t size_ = 0;
};
inline bool utf8() {
@ -67,12 +71,12 @@ inline int width() {
#if defined TIOCGWINSZ
struct winsize ws;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
w = std::max(static_cast<int>(ws.ws_col), 0); // not sure if ws_col can be less than 0
w = (std::max)(static_cast<int>(ws.ws_col), 0); // not sure if ws_col can be less than 0
#endif
env_t env("COLUMNS");
const int col = std::max(static_cast<int>(env), 0);
const int col = (std::max)(static_cast<int>(env), 0);
// if both t and w are set, COLUMNS may be used to restrict width
return w == 0 ? col : std::min(col, w);
return w == 0 ? col : (std::min)(col, w);
}
} // namespace term_info

View File

@ -91,7 +91,7 @@ struct sample_type;
namespace accumulators {
template <class ValueType = double>
template <class ValueType = double, bool ThreadSafe = false>
class count;
template <class ValueType = double>
@ -110,9 +110,7 @@ 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 {};
struct is_thread_safe;
} // namespace accumulators

View File

@ -323,7 +323,7 @@ public:
indexed_range(histogram_type& hist, Iterable&& range)
: begin_(hist.begin(), hist), end_(hist.end(), hist) {
auto r_begin = std::begin(range);
assert(std::distance(r_begin, std::end(range)) == hist.rank());
assert(std::distance(r_begin, std::end(range)) == static_cast<int>(hist.rank()));
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), r_begin,
stride = std::size_t{1},

View File

@ -9,6 +9,7 @@
#include <algorithm>
#include <boost/core/nvp.hpp>
#include <boost/histogram/accumulators/is_thread_safe.hpp>
#include <boost/histogram/detail/array_wrapper.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/iterator_adaptor.hpp>

View File

@ -97,6 +97,8 @@ if (Threads_FOUND)
LINK_LIBRARIES Threads::Threads)
boost_test(TYPE run SOURCES storage_adaptor_threaded_test.cpp
LINK_LIBRARIES Threads::Threads)
boost_test(TYPE run SOURCES accumulators_count_thread_safe_test.cpp
LINK_LIBRARIES Threads::Threads)
boost_test(TYPE run SOURCES accumulators_thread_safe_test.cpp
LINK_LIBRARIES Threads::Threads)

View File

@ -82,8 +82,6 @@ alias cxx14 :
[ run histogram_growing_test.cpp ]
[ run histogram_mixed_test.cpp ]
[ run histogram_operators_test.cpp ]
[ run histogram_ostream_test.cpp : : : <testing.launcher>"env LANG=UTF" ]
[ run histogram_ostream_ascii_test.cpp : : : <testing.launcher>"env LANG=FOO COLUMNS=20" ]
[ run histogram_test.cpp ]
[ run indexed_test.cpp ]
[ run storage_adaptor_test.cpp ]
@ -123,11 +121,18 @@ alias failure :
alias threading :
[ run histogram_threaded_test.cpp ]
[ run storage_adaptor_threaded_test.cpp ]
[ run accumulators_count_thread_safe_test.cpp ]
[ run accumulators_thread_safe_test.cpp ]
:
<threading>multi
;
# tests do not work on windows, because environment variables cannot be set
alias not_windows :
[ run histogram_ostream_test.cpp : : : <testing.launcher>"LANG=UTF" ]
[ run histogram_ostream_ascii_test.cpp : : : <testing.launcher>"LANG=FOO COLUMNS=20" ]
;
# warnings are off for these other boost libraries, which tend to be not warning-free
alias accumulators : [ run boost_accumulators_support_test.cpp ] : <warnings>off ;
alias range : [ run boost_range_support_test.cpp ] : <warnings>off ;
@ -152,10 +157,10 @@ alias libserial :
alias minimal : cxx14 cxx17 failure threading ;
# all tests
alias all : minimal odr accumulators range units serialization ;
alias all : minimal not_windows odr accumulators range units serialization ;
# all except "failure", because it is distracting during development
alias develop : cxx14 cxx17 threading odr accumulators range units serialization ;
alias develop : cxx14 cxx17 threading not_windows odr accumulators range units serialization ;
explicit minimal ;
explicit all ;
@ -164,6 +169,7 @@ explicit cxx14 ;
explicit cxx17 ;
explicit failure ;
explicit threading ;
explicit not_windows ;
explicit accumulators ;
explicit range ;
explicit units ;

View File

@ -13,8 +13,9 @@
using namespace boost::histogram;
using namespace std::literals;
int main() {
using c_t = accumulators::count<double>;
template <class T, bool B>
void run_tests() {
using c_t = accumulators::count<T, B>;
c_t c;
++c;
@ -26,8 +27,8 @@ int main() {
c += 2;
BOOST_TEST_EQ(str(c), "3"s);
BOOST_TEST_EQ(c, 3);
BOOST_TEST_NE(c, 2);
BOOST_TEST_EQ(c, static_cast<T>(3));
BOOST_TEST_NE(c, static_cast<T>(2));
c_t one(1), two(2), one_copy(1);
BOOST_TEST_LT(one, two);
@ -38,6 +39,13 @@ int main() {
BOOST_TEST_GE(one, one_copy);
BOOST_TEST_EQ(c_t{} += c_t{}, c_t{});
}
int main() {
run_tests<int, false>();
run_tests<int, true>();
run_tests<float, false>();
run_tests<float, true>();
return boost::report_errors();
}

View File

@ -0,0 +1,93 @@
// Copyright 2015-2018 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/count.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <sstream>
#include <thread>
#include "throw_exception.hpp"
#include "utility_str.hpp"
using namespace boost::histogram;
using namespace std::literals;
constexpr int N = 10000;
template <class F>
void parallel(F f) {
auto g = [&]() {
for (int i = 0; i < N; ++i) f();
};
std::thread a(g), b(g), c(g), d(g);
a.join();
b.join();
c.join();
d.join();
}
template <class T>
void test_on() {
using ts_t = accumulators::count<T, true>;
// default ctor
{
ts_t i;
BOOST_TEST_EQ(i, static_cast<T>(0));
}
// ctor from value
{
ts_t i{1001};
BOOST_TEST_EQ(i, static_cast<T>(1001));
BOOST_TEST_EQ(str(i), "1001"s);
}
// add null
BOOST_TEST_EQ(ts_t{} += ts_t{}, ts_t{});
// add non-null
BOOST_TEST_EQ((ts_t{} += ts_t{2}), (ts_t{2}));
// operator++
{
ts_t t;
parallel([&]() { ++t; });
BOOST_TEST_EQ(t, static_cast<T>(4 * N));
}
// operator+= with value
{
ts_t t;
parallel([&]() { t += 2; });
BOOST_TEST_EQ(t, static_cast<T>(8 * N));
}
// operator+= with another thread-safe
{
ts_t t, u;
u = 2;
parallel([&]() { t += u; });
BOOST_TEST_EQ(t, static_cast<T>(8 * N));
}
}
int main() {
test_on<int>();
test_on<float>();
// copy and assignment from other thread-safe
{
accumulators::count<char, true> r{1};
accumulators::count<int, true> a{r}, b;
b = r;
BOOST_TEST_EQ(a, 1);
BOOST_TEST_EQ(b, 1);
}
return boost::report_errors();
}

View File

@ -8,86 +8,31 @@
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/accumulators/thread_safe.hpp>
#include <sstream>
#include <thread>
#include "throw_exception.hpp"
#include "utility_str.hpp"
using namespace boost::histogram;
using namespace std::literals;
constexpr int N = 10000;
template <class F>
void parallel(F f) {
auto g = [&]() {
for (int i = 0; i < N; ++i) f();
};
std::thread a(g), b(g), c(g), d(g);
a.join();
b.join();
c.join();
d.join();
}
template <class T>
void test_on() {
using ts_t = accumulators::thread_safe<T>;
// default ctor
{
ts_t i;
BOOST_TEST_EQ(i, static_cast<T>(0));
}
// ctor from value
{
ts_t i{1001};
BOOST_TEST_EQ(i, static_cast<T>(1001));
BOOST_TEST_EQ(str(i), "1001"s);
}
// add null
BOOST_TEST_EQ(ts_t{} += ts_t{}, ts_t{});
// add non-null
BOOST_TEST_EQ((ts_t{} += ts_t{2}), (ts_t{2}));
// operator++
{
ts_t t;
parallel([&]() { ++t; });
BOOST_TEST_EQ(t, static_cast<T>(4 * N));
}
// operator+= with value
{
ts_t t;
parallel([&]() { t += 2; });
BOOST_TEST_EQ(t, static_cast<T>(8 * N));
}
// operator+= with another thread_safe
{
ts_t t, u;
u = 2;
parallel([&]() { t += u; });
BOOST_TEST_EQ(t, static_cast<T>(8 * N));
}
}
#include <boost/histogram/detail/ignore_deprecation_warning_begin.hpp>
int main() {
test_on<int>();
test_on<float>();
using ts_t = accumulators::thread_safe<int>;
// copy and assignment from other thread_safe
{
accumulators::thread_safe<char> r{1};
accumulators::thread_safe<int> a{r}, b;
b = r;
BOOST_TEST_EQ(a, 1);
BOOST_TEST_EQ(b, 1);
}
ts_t i;
++i;
i += 1000;
BOOST_TEST_EQ(i, 1001);
BOOST_TEST_EQ(str(i), "1001"s);
ts_t j{5};
i = j;
BOOST_TEST_EQ(i, 5);
BOOST_TEST_EQ(ts_t{} += ts_t{}, ts_t{});
return boost::report_errors();
}
#include <boost/histogram/detail/ignore_deprecation_warning_end.hpp>

View File

@ -7,8 +7,9 @@
#include <array>
#include <boost/config.hpp>
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators.hpp>
#include <boost/histogram/accumulators/mean.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/accumulators/weighted_mean.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#include <boost/histogram/axis/category.hpp>
#include <boost/histogram/axis/integer.hpp>

View File

@ -5,12 +5,7 @@
// 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/sum.hpp>
#include <boost/histogram/accumulators/thread_safe.hpp>
#include <boost/histogram/accumulators/weighted_sum.hpp>
#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/make_histogram.hpp>
#include <boost/histogram/ostream.hpp>
#include <limits>
#include <sstream>

View File

@ -109,9 +109,9 @@ void run_tests() {
BOOST_TEST_CSTR_EQ(str(h, 40).c_str(), expected);
}
// regular with accumulators::thread_safe
// regular with thread-safe accumulators::count
{
auto h = make_s(Tag(), dense_storage<accumulators::thread_safe<double>>(),
auto h = make_s(Tag(), dense_storage<accumulators::count<double, true>>(),
R2(3, -0.5, 1.0));
h.at(0) = 1;
h.at(1) = 10;
@ -129,6 +129,28 @@ void run_tests() {
BOOST_TEST_CSTR_EQ(str(h, 40).c_str(), expected);
}
// regular with accumulators::thread_safe
{
#include <boost/histogram/detail/ignore_deprecation_warning_begin.hpp>
auto h =
make_s(Tag(), dense_storage<accumulators::thread_safe<int>>(), R2(3, -0.5, 1.0));
h.at(0) = 1;
h.at(1) = 10;
h.at(2) = 5;
const auto expected = "BEGIN\n"
"histogram(regular(3, -0.5, 1, options=none))\n"
" ┌───────────────────────┐\n"
"[-0.5, 0) 1 │██▎ │\n"
"[ 0, 0.5) 10 │██████████████████████ │\n"
"[ 0.5, 1) 5 │███████████ │\n"
" └───────────────────────┘\n"
"END";
BOOST_TEST_CSTR_EQ(str(h, 40).c_str(), expected);
#include <boost/histogram/detail/ignore_deprecation_warning_end.hpp>
}
// regular with accumulators::sum
{
auto h = make_s(Tag(), dense_storage<accumulators::sum<double>>(), R2(3, -0.5, 1.0));
@ -366,7 +388,7 @@ void run_tests() {
int main() {
run_tests<static_tag>();
// run_tests<dynamic_tag>();
run_tests<dynamic_tag>();
{
// cannot make empty static histogram

View File

@ -6,8 +6,9 @@
#include <boost/core/ignore_unused.hpp>
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators.hpp>
#include <boost/histogram/accumulators/mean.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/accumulators/weighted_mean.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#include <boost/histogram/axis.hpp>
#include <boost/histogram/axis/ostream.hpp>

View File

@ -5,7 +5,7 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators/thread_safe.hpp>
#include <boost/histogram/accumulators/count.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/axis/ostream.hpp>
@ -28,7 +28,7 @@ void fill_test(const A1& a1, const A2& a2, const X& x, const Y& y) {
auto xy = {x, y};
h1.fill(xy);
auto h2 = make_s(Tag{}, dense_storage<accumulators::thread_safe<int>>(), a1, a2);
auto h2 = make_s(Tag{}, dense_storage<accumulators::count<int, true>>(), a1, a2);
auto run = [&h2, &x, &y](int k) {
constexpr auto shift = n_fill / 4;
auto xit = x.cbegin() + k * shift;

View File

@ -5,11 +5,12 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <array>
#include <cassert>
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators/count.hpp>
#include <boost/histogram/accumulators/thread_safe.hpp>
#include <boost/histogram/serialization.hpp>
#include <boost/histogram/storage_adaptor.hpp>
#include <cassert>
#include <map>
#include <vector>
#include "throw_exception.hpp"
@ -40,8 +41,12 @@ int main(int argc, char** argv) {
join(argv[1], "storage_adaptor_serialization_test_array_unsigned.xml"));
test_serialization<std::map<std::size_t, double>>(
join(argv[1], "storage_adaptor_serialization_test_map_double.xml"));
test_serialization<std::vector<accumulators::count<int, true>>>(
join(argv[1], "storage_adaptor_serialization_test_vector_thread_safe_int.xml"));
#include <boost/histogram/detail/ignore_deprecation_warning_begin.hpp>
test_serialization<std::vector<accumulators::thread_safe<int>>>(
join(argv[1], "storage_adaptor_serialization_test_vector_thread_safe_int.xml"));
#include <boost/histogram/detail/ignore_deprecation_warning_end.hpp>
return boost::report_errors();
}

View File

@ -5,10 +5,10 @@
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators/count.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/accumulators/thread_safe.hpp>
#include "throw_exception.hpp"
#include <boost/histogram/storage_adaptor.hpp>
#include "throw_exception.hpp"
#include <array>
#include <deque>
@ -48,7 +48,7 @@ void tests() {
}
int main() {
using ts_int = accumulators::thread_safe<int>;
using ts_int = accumulators::count<int, true>;
tests<std::vector<ts_int>>();
tests<std::array<ts_int, 100>>();
tests<std::deque<ts_int>>();