diff --git a/.github/workflows/fast.yml b/.github/workflows/fast.yml index f54a97bb..00c9d64f 100644 --- a/.github/workflows/fast.yml +++ b/.github/workflows/fast.yml @@ -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 diff --git a/.github/workflows/slow.yml b/.github/workflows/slow.yml index 221cbb0c..6cf288fa 100644 --- a/.github/workflows/slow.yml +++ b/.github/workflows/slow.yml @@ -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 diff --git a/doc/guide.qbk b/doc/guide.qbk index 2ddfe0d8..a0be7602 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -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.] diff --git a/examples/guide_parallel_filling.cpp b/examples/guide_parallel_filling.cpp index f4a23c9c..7d37fbbb 100644 --- a/examples/guide_parallel_filling.cpp +++ b/examples/guide_parallel_filling.cpp @@ -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>(), + auto h = make_histogram_with(dense_storage>(), axis::integer<>(0, 10)); /* diff --git a/include/boost/histogram/accumulators/count.hpp b/include/boost/histogram/accumulators/count.hpp index 2fe1dbfb..df947b96 100644 --- a/include/boost/histogram/accumulators/count.hpp +++ b/include/boost/histogram/accumulators/count.hpp @@ -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 +#include #include // for count<> #include // 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` - over `dense_storage>`, 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 +template class count { + using internal_type = + std::conditional_t, 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 - count(const count& c) noexcept : count(c.value()) {} + template + count(const count& 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 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 -struct common_type, - boost::histogram::accumulators::count> { - using type = boost::histogram::accumulators::count>; +template +struct common_type, + boost::histogram::accumulators::count> { + using type = boost::histogram::accumulators::count, (B1 || B2)>; }; } // namespace std #endif diff --git a/include/boost/histogram/accumulators/is_thread_safe.hpp b/include/boost/histogram/accumulators/is_thread_safe.hpp new file mode 100644 index 00000000..099f7850 --- /dev/null +++ b/include/boost/histogram/accumulators/is_thread_safe.hpp @@ -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 +#include +#include + +namespace boost { +namespace histogram { +namespace detail { + +template +constexpr bool is_thread_safe_impl(priority<0>) { + return false; +} + +template +constexpr auto is_thread_safe_impl(priority<1>) -> decltype(T::thread_safe()) { + return T::thread_safe(); +} + +} // namespace detail + +namespace accumulators { + +template +struct is_thread_safe + : std::integral_constant(detail::priority<1>{})> {}; + +} // namespace accumulators +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/accumulators/ostream.hpp b/include/boost/histogram/accumulators/ostream.hpp index b3fff27f..7519bf0e 100644 --- a/include/boost/histogram/accumulators/ostream.hpp +++ b/include/boost/histogram/accumulators/ostream.hpp @@ -54,9 +54,9 @@ std::basic_ostream& handle_nonzero_width( namespace accumulators { -template +template std::basic_ostream& operator<<(std::basic_ostream& os, - const count& x) { + const count& x) { return os << x.value(); } @@ -92,12 +92,15 @@ std::basic_ostream& operator<<(std::basic_ostream& return detail::handle_nonzero_width(os, x); } +#include template std::basic_ostream& operator<<(std::basic_ostream& os, const thread_safe& x) { os << static_cast(x); return os; } +#include + } // namespace accumulators } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/accumulators/thread_safe.hpp b/include/boost/histogram/accumulators/thread_safe.hpp index 0ea528b9..94161c5a 100644 --- a/include/boost/histogram/accumulators/thread_safe.hpp +++ b/include/boost/histogram/accumulators/thread_safe.hpp @@ -9,15 +9,18 @@ #include #include -#include -#include +#include #include namespace boost { namespace histogram { namespace accumulators { -/** Thread-safe adaptor for integral and floating point numbers. +// cannot use new mechanism with accumulators::thread_safe +template +struct is_thread_safe> : 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 thread_safe { - static_assert(std::is_arithmetic(), ""); - +class [[deprecated("use count instead; " + "thread_safe will be removed in boost-1.79")]] thread_safe + : public std::atomic { public: using value_type = T; - using const_reference = const T&; - - thread_safe() noexcept : value_{static_cast(0)} {} + using super_t = std::atomic; + thread_safe() noexcept : super_t(static_cast(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 - thread_safe(const thread_safe& o) noexcept : value_{o.value()} {} - - /// Allow assignment from other thread_safe. - template - thread_safe& operator=(const thread_safe& 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(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 - 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 - auto increment_impl(detail::priority<1>) -> decltype(++std::declval>()) { - return ++value_; - } - - template - auto add_impl(detail::priority<1>, const_reference x) - -> decltype(std::declval>().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 - void increment_impl(detail::priority<0>) { - operator+=(static_cast(1)); - } - - // workaround for floating point numbers in C++14, obsolete in C++20 - template - 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 value_; }; } // namespace accumulators diff --git a/include/boost/histogram/detail/atomic_number.hpp b/include/boost/histogram/detail/atomic_number.hpp new file mode 100644 index 00000000..1e2f0233 --- /dev/null +++ b/include/boost/histogram/detail/atomic_number.hpp @@ -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 +#include +#include + +namespace boost { +namespace histogram { +namespace detail { + +// copyable arithmetic type with thread-safe operator++ and operator+= +template +struct atomic_number : std::atomic { + static_assert(std::is_arithmetic(), ""); + + using base_t = std::atomic; + using std::atomic::atomic; + + atomic_number() noexcept = default; + atomic_number(const atomic_number& o) noexcept : std::atomic{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(*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(*this), x, priority<1>{}); + return *this; + } + +private: + // for integral types + template + auto increment_impl(std::atomic& a, priority<1>) noexcept -> decltype(++a) { + return ++a; + } + + // fallback implementation for floating point types + template + void increment_impl(std::atomic&, priority<0>) noexcept { + this->operator+=(static_cast(1)); + } + + // always available for integral types, in C++20 also available for float + template + static auto add_impl(std::atomic& a, const U& x, priority<1>) noexcept + -> decltype(a += x) { + return a += x; + } + + // pre-C++20 fallback implementation for floating point + template + static void add_impl(std::atomic& 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 diff --git a/include/boost/histogram/detail/ignore_deprecation_warning_begin.hpp b/include/boost/histogram/detail/ignore_deprecation_warning_begin.hpp new file mode 100644 index 00000000..b9ad543d --- /dev/null +++ b/include/boost/histogram/detail/ignore_deprecation_warning_begin.hpp @@ -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 diff --git a/include/boost/histogram/detail/ignore_deprecation_warning_end.hpp b/include/boost/histogram/detail/ignore_deprecation_warning_end.hpp new file mode 100644 index 00000000..0bf6273c --- /dev/null +++ b/include/boost/histogram/detail/ignore_deprecation_warning_end.hpp @@ -0,0 +1,6 @@ +#if (defined(__GNUC__) || defined(__clang__)) +#pragma GCC diagnostic pop + +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/include/boost/histogram/detail/term_info.hpp b/include/boost/histogram/detail/term_info.hpp index 3de85e41..8d34a4ba 100644 --- a/include/boost/histogram/detail/term_info.hpp +++ b/include/boost/histogram/detail/term_info.hpp @@ -7,13 +7,15 @@ #ifndef BOOST_HISTOGRAM_DETAIL_TERM_INFO_HPP #define BOOST_HISTOGRAM_DETAIL_TERM_INFO_HPP +#include + #if defined __has_include #if __has_include() && __has_include() #include #include #endif #endif -#include +#include #include #include @@ -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(ws.ws_col), 0); // not sure if ws_col can be less than 0 + w = (std::max)(static_cast(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(env), 0); + const int col = (std::max)(static_cast(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 diff --git a/include/boost/histogram/fwd.hpp b/include/boost/histogram/fwd.hpp index 0e231256..4826ab3e 100644 --- a/include/boost/histogram/fwd.hpp +++ b/include/boost/histogram/fwd.hpp @@ -91,7 +91,7 @@ struct sample_type; namespace accumulators { -template +template class count; template @@ -110,9 +110,7 @@ template class thread_safe; template -struct is_thread_safe : std::false_type {}; -template -struct is_thread_safe> : std::true_type {}; +struct is_thread_safe; } // namespace accumulators diff --git a/include/boost/histogram/indexed.hpp b/include/boost/histogram/indexed.hpp index 169b8842..47507f0b 100644 --- a/include/boost/histogram/indexed.hpp +++ b/include/boost/histogram/indexed.hpp @@ -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(hist.rank())); begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), r_begin, stride = std::size_t{1}, diff --git a/include/boost/histogram/storage_adaptor.hpp b/include/boost/histogram/storage_adaptor.hpp index 7c5539da..a30fb11c 100644 --- a/include/boost/histogram/storage_adaptor.hpp +++ b/include/boost/histogram/storage_adaptor.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a0e986f0..29c911ca 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/Jamfile b/test/Jamfile index 149fef84..ee82ad3b 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -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 : : : "env LANG=UTF" ] - [ run histogram_ostream_ascii_test.cpp : : : "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 ] : multi ; +# tests do not work on windows, because environment variables cannot be set +alias not_windows : + [ run histogram_ostream_test.cpp : : : "LANG=UTF" ] + [ run histogram_ostream_ascii_test.cpp : : : "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 ] : off ; alias range : [ run boost_range_support_test.cpp ] : 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 ; diff --git a/test/accumulators_count_test.cpp b/test/accumulators_count_test.cpp index abd3fc2f..4b956e67 100644 --- a/test/accumulators_count_test.cpp +++ b/test/accumulators_count_test.cpp @@ -13,8 +13,9 @@ using namespace boost::histogram; using namespace std::literals; -int main() { - using c_t = accumulators::count; +template +void run_tests() { + using c_t = accumulators::count; 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(3)); + BOOST_TEST_NE(c, static_cast(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(); + run_tests(); + run_tests(); + run_tests(); return boost::report_errors(); } diff --git a/test/accumulators_count_thread_safe_test.cpp b/test/accumulators_count_thread_safe_test.cpp new file mode 100644 index 00000000..90af2e0d --- /dev/null +++ b/test/accumulators_count_thread_safe_test.cpp @@ -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 +#include +#include +#include +#include +#include "throw_exception.hpp" +#include "utility_str.hpp" + +using namespace boost::histogram; +using namespace std::literals; + +constexpr int N = 10000; + +template +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 +void test_on() { + using ts_t = accumulators::count; + + // default ctor + { + ts_t i; + BOOST_TEST_EQ(i, static_cast(0)); + } + + // ctor from value + { + ts_t i{1001}; + BOOST_TEST_EQ(i, static_cast(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(4 * N)); + } + + // operator+= with value + { + ts_t t; + parallel([&]() { t += 2; }); + BOOST_TEST_EQ(t, static_cast(8 * N)); + } + + // operator+= with another thread-safe + { + ts_t t, u; + u = 2; + parallel([&]() { t += u; }); + BOOST_TEST_EQ(t, static_cast(8 * N)); + } +} + +int main() { + test_on(); + test_on(); + + // copy and assignment from other thread-safe + { + accumulators::count r{1}; + accumulators::count a{r}, b; + b = r; + BOOST_TEST_EQ(a, 1); + BOOST_TEST_EQ(b, 1); + } + + return boost::report_errors(); +} diff --git a/test/accumulators_thread_safe_test.cpp b/test/accumulators_thread_safe_test.cpp index 6a4f76ca..654d6797 100644 --- a/test/accumulators_thread_safe_test.cpp +++ b/test/accumulators_thread_safe_test.cpp @@ -8,86 +8,31 @@ #include #include #include -#include #include "throw_exception.hpp" #include "utility_str.hpp" using namespace boost::histogram; using namespace std::literals; -constexpr int N = 10000; - -template -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 -void test_on() { - using ts_t = accumulators::thread_safe; - - // default ctor - { - ts_t i; - BOOST_TEST_EQ(i, static_cast(0)); - } - - // ctor from value - { - ts_t i{1001}; - BOOST_TEST_EQ(i, static_cast(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(4 * N)); - } - - // operator+= with value - { - ts_t t; - parallel([&]() { t += 2; }); - BOOST_TEST_EQ(t, static_cast(8 * N)); - } - - // operator+= with another thread_safe - { - ts_t t, u; - u = 2; - parallel([&]() { t += u; }); - BOOST_TEST_EQ(t, static_cast(8 * N)); - } -} +#include int main() { - test_on(); - test_on(); + using ts_t = accumulators::thread_safe; - // copy and assignment from other thread_safe - { - accumulators::thread_safe r{1}; - accumulators::thread_safe 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 diff --git a/test/histogram_fill_test.cpp b/test/histogram_fill_test.cpp index 9d946233..807b2061 100644 --- a/test/histogram_fill_test.cpp +++ b/test/histogram_fill_test.cpp @@ -7,8 +7,9 @@ #include #include #include -#include +#include #include +#include #include #include #include diff --git a/test/histogram_ostream_ascii_test.cpp b/test/histogram_ostream_ascii_test.cpp index 4745ac69..db38ff60 100644 --- a/test/histogram_ostream_ascii_test.cpp +++ b/test/histogram_ostream_ascii_test.cpp @@ -5,12 +5,7 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include -#include -#include -#include #include -#include #include #include #include diff --git a/test/histogram_ostream_test.cpp b/test/histogram_ostream_test.cpp index 0d4429ef..ed52399a 100644 --- a/test/histogram_ostream_test.cpp +++ b/test/histogram_ostream_test.cpp @@ -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>(), + auto h = make_s(Tag(), dense_storage>(), 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 + auto h = + make_s(Tag(), dense_storage>(), 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 + } + // regular with accumulators::sum { auto h = make_s(Tag(), dense_storage>(), R2(3, -0.5, 1.0)); @@ -366,7 +388,7 @@ void run_tests() { int main() { run_tests(); - // run_tests(); + run_tests(); { // cannot make empty static histogram diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index 85ec9be3..8a01c75b 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -6,8 +6,9 @@ #include #include -#include +#include #include +#include #include #include #include diff --git a/test/histogram_threaded_test.cpp b/test/histogram_threaded_test.cpp index 1db41ea9..ce3ddbc3 100644 --- a/test/histogram_threaded_test.cpp +++ b/test/histogram_threaded_test.cpp @@ -5,7 +5,7 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include +#include #include #include #include @@ -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>(), a1, a2); + auto h2 = make_s(Tag{}, dense_storage>(), a1, a2); auto run = [&h2, &x, &y](int k) { constexpr auto shift = n_fill / 4; auto xit = x.cbegin() + k * shift; diff --git a/test/storage_adaptor_serialization_test.cpp b/test/storage_adaptor_serialization_test.cpp index bc7ac96b..c4e9e3c9 100644 --- a/test/storage_adaptor_serialization_test.cpp +++ b/test/storage_adaptor_serialization_test.cpp @@ -5,11 +5,12 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include #include +#include #include #include #include +#include #include #include #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>( join(argv[1], "storage_adaptor_serialization_test_map_double.xml")); + test_serialization>>( + join(argv[1], "storage_adaptor_serialization_test_vector_thread_safe_int.xml")); +#include test_serialization>>( join(argv[1], "storage_adaptor_serialization_test_vector_thread_safe_int.xml")); +#include return boost::report_errors(); } diff --git a/test/storage_adaptor_threaded_test.cpp b/test/storage_adaptor_threaded_test.cpp index 0ba47534..dfb2848b 100644 --- a/test/storage_adaptor_threaded_test.cpp +++ b/test/storage_adaptor_threaded_test.cpp @@ -5,10 +5,10 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include #include -#include -#include "throw_exception.hpp" #include +#include "throw_exception.hpp" #include #include @@ -48,7 +48,7 @@ void tests() { } int main() { - using ts_int = accumulators::thread_safe; + using ts_int = accumulators::count; tests>(); tests>(); tests>();