Skip to content

Commit 2ff8e2a

Browse files
committed
[hist] Implement initial RHistConcurrentFiller
In order to support efficient concurrent filling of RHist with global histogram statistics, we keep one local RHistStats object per RHistFillContext.
1 parent a9f0f9b commit 2ff8e2a

File tree

9 files changed

+396
-1
lines changed

9 files changed

+396
-1
lines changed

hist/histv7/doc/CodeArchitecture.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ It can be used as a template argument to `RHistEngine` and `RHist`.
6262
A wrapper `struct` for a single `double` value, used for weighted filling to distinguish its type.
6363
Objects of this type are passed by value.
6464

65+
## Classes for Concurrent Filling
66+
67+
### `RHistConcurrentFiller`
68+
69+
A class to orchestrate concurrent filling of `RHist` by creating (multiple) fill contexts.
70+
71+
### `RHistFillContext`
72+
73+
Parallel user code uses contexts to fill `RHist`s concurrently.
74+
Each instance has a local `RHistStats` object to avoid contention on the global histogram statistics.
75+
6576
## Auxiliary Classes
6677

6778
### `RBinIndex`

hist/histv7/doc/ConcurrentFilling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ For large histograms and reasonable data, contention on individual bins is expec
4646
On the other hand, updates of the (global) histogram statistics (`RHistStats`) can easily lead to contention.
4747
For this reason, `RHist` does **not** offer a `FillAtomic` method because it cannot be implemented efficiently.
4848
Instead, the user has to create a `RHistConcurrentFiller` and (potentially many) `RHistFillContext`s.
49-
These will work together to accumulate the (global) histogram statistics during concurrent filling.
49+
These work together to accumulate the (global) histogram statistics during concurrent filling.

hist/histv7/headers.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ set(histv7_headers
66
ROOT/RCategoricalAxis.hxx
77
ROOT/RHist.hxx
88
ROOT/RHistAutoAxisFiller.hxx
9+
ROOT/RHistConcurrentFiller.hxx
910
ROOT/RHistEngine.hxx
11+
ROOT/RHistFillContext.hxx
1012
ROOT/RHistStats.hxx
1113
ROOT/RHistUtils.hxx
1214
ROOT/RLinearizedIndex.hxx

hist/histv7/inc/ROOT/RHist.hxx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ class TBuffer;
2424
namespace ROOT {
2525
namespace Experimental {
2626

27+
// forward declaration for friend declaration
28+
template <typename BinContentType>
29+
class RHistFillContext;
30+
2731
/**
2832
A histogram for aggregation of data along multiple dimensions.
2933
@@ -55,6 +59,8 @@ Feedback is welcome!
5559
*/
5660
template <typename BinContentType>
5761
class RHist final {
62+
friend class RHistFillContext<BinContentType>;
63+
5864
/// The histogram engine including the bin contents.
5965
RHistEngine<BinContentType> fEngine;
6066
/// The global histogram statistics.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/// \file
2+
/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
3+
/// Feedback is welcome!
4+
5+
#ifndef ROOT_RHistConcurrentFiller
6+
#define ROOT_RHistConcurrentFiller
7+
8+
#include "RHist.hxx"
9+
#include "RHistEngine.hxx"
10+
#include "RHistFillContext.hxx"
11+
#include "RWeight.hxx"
12+
13+
#include <exception>
14+
#include <memory>
15+
#include <mutex>
16+
#include <stdexcept>
17+
#include <vector>
18+
19+
namespace ROOT {
20+
namespace Experimental {
21+
22+
/**
23+
A histogram filler to concurrently fill an RHist.
24+
25+
\code
26+
auto hist = std::make_shared<ROOT::Experimental::RHist<int>>(10, std::make_pair(5, 15));
27+
{
28+
ROOT::Experimental::RHistConcurrentFiller filler(hist);
29+
auto context = filler.CreateFillContext();
30+
context.Fill(8.5);
31+
}
32+
// hist->GetBinContent(ROOT::Experimental::RBinIndex(3)) will return 1
33+
\endcode
34+
35+
\warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
36+
Feedback is welcome!
37+
*/
38+
template <typename BinContentType>
39+
class RHistConcurrentFiller final {
40+
/// A pointer to the filled histogram
41+
std::shared_ptr<RHist<BinContentType>> fHist;
42+
43+
/// Mutex to protect access to the list of fill contexts (not for filling itself!)
44+
std::mutex fMutex;
45+
/// The list of fill contexts, for checks during destruction
46+
std::vector<std::weak_ptr<RHistFillContext<BinContentType>>> fFillContexts;
47+
48+
public:
49+
/// Create a filler object.
50+
///
51+
/// \param[in] hist a pointer to the histogram
52+
explicit RHistConcurrentFiller(std::shared_ptr<RHist<BinContentType>> hist) : fHist(hist)
53+
{
54+
if (!hist) {
55+
throw std::invalid_argument("hist must not be nullptr");
56+
}
57+
}
58+
59+
RHistConcurrentFiller(const RHistConcurrentFiller<BinContentType> &) = delete;
60+
RHistConcurrentFiller(RHistConcurrentFiller<BinContentType> &&) = delete;
61+
RHistConcurrentFiller<BinContentType> &operator=(const RHistConcurrentFiller<BinContentType> &) = delete;
62+
RHistConcurrentFiller<BinContentType> &operator=(RHistConcurrentFiller<BinContentType> &&) = delete;
63+
64+
~RHistConcurrentFiller()
65+
{
66+
for (const auto &context : fFillContexts) {
67+
if (!context.expired()) {
68+
// According to C++ Core Guideline C.36 "A destructor must not fail" and (C.37) "If a destructor tries to
69+
// exit with an exception, it’s a bad design error and the program had better terminate".
70+
std::terminate(); // GCOVR_EXCL_LINE
71+
}
72+
}
73+
}
74+
75+
const std::shared_ptr<RHist<BinContentType>> &GetHist() const { return fHist; }
76+
77+
/// Create a new context for concurrent filling.
78+
std::shared_ptr<RHistFillContext<BinContentType>> CreateFillContext()
79+
{
80+
// Cannot use std::make_shared because the constructor of RHistFillContext is private. Also it would mean that the
81+
// (direct) memory of all contexts stays around until the vector of weak_ptr's is cleared.
82+
std::shared_ptr<RHistFillContext<BinContentType>> context(new RHistFillContext<BinContentType>(*fHist));
83+
84+
{
85+
std::lock_guard g(fMutex);
86+
fFillContexts.push_back(context);
87+
}
88+
89+
return context;
90+
}
91+
};
92+
93+
} // namespace Experimental
94+
} // namespace ROOT
95+
96+
#endif
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/// \file
2+
/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
3+
/// Feedback is welcome!
4+
5+
#ifndef ROOT_RHistFillContext
6+
#define ROOT_RHistFillContext
7+
8+
#include "RHist.hxx"
9+
#include "RHistEngine.hxx"
10+
#include "RHistStats.hxx"
11+
12+
namespace ROOT {
13+
namespace Experimental {
14+
15+
// forward declaration for friend declaration
16+
template <typename BinContentType>
17+
class RHistConcurrentFiller;
18+
19+
/**
20+
A context to concurrently fill an RHist.
21+
22+
\sa RHistConcurrentFiller
23+
24+
\warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
25+
Feedback is welcome!
26+
*/
27+
template <typename BinContentType>
28+
class RHistFillContext final {
29+
friend class RHistConcurrentFiller<BinContentType>;
30+
31+
private:
32+
/// A pointer to the filled histogram
33+
RHist<BinContentType> *fHist;
34+
35+
/// Local histogram statistics
36+
RHistStats fStats;
37+
38+
/// \sa RHistConcurrentFiller::CreateFillContent()
39+
explicit RHistFillContext(RHist<BinContentType> &hist) : fHist(&hist), fStats(hist.GetNDimensions()) {}
40+
RHistFillContext(const RHistFillContext<BinContentType> &) = delete;
41+
RHistFillContext(RHistFillContext<BinContentType> &&) = default;
42+
RHistFillContext<BinContentType> &operator=(const RHistFillContext<BinContentType> &) = delete;
43+
RHistFillContext<BinContentType> &operator=(RHistFillContext<BinContentType> &&) = default;
44+
45+
public:
46+
~RHistFillContext() { Flush(); }
47+
48+
/// Fill an entry into the histogram.
49+
///
50+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
51+
/// discarded.
52+
///
53+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
54+
/// converted for the axis type at run-time.
55+
///
56+
/// \param[in] args the arguments for each axis
57+
/// \sa RHist::Fill(const std::tuple<A...> &args)
58+
template <typename... A>
59+
void Fill(const std::tuple<A...> &args)
60+
{
61+
fHist->fEngine.FillAtomic(args);
62+
fStats.Fill(args);
63+
}
64+
65+
/// Fill an entry into the histogram with a weight.
66+
///
67+
/// This overload is not available for integral bin content types (see \ref RHistEngine::SupportsWeightedFilling).
68+
///
69+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
70+
/// discarded.
71+
///
72+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
73+
/// converted for the axis type at run-time.
74+
///
75+
/// \param[in] args the arguments for each axis
76+
/// \param[in] weight the weight for this entry
77+
/// \sa RHist::Fill(const std::tuple<A...> &args, RWeight weight)
78+
template <typename... A>
79+
void Fill(const std::tuple<A...> &args, RWeight weight)
80+
{
81+
fHist->fEngine.FillAtomic(args, weight);
82+
fStats.Fill(args, weight);
83+
}
84+
85+
/// Fill an entry into the histogram.
86+
///
87+
/// For weighted filling, pass an RWeight as the last argument. This is not available for integral bin content types
88+
/// (see \ref RHistEngine::SupportsWeightedFilling).
89+
///
90+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
91+
/// discarded.
92+
///
93+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
94+
/// converted for the axis type at run-time.
95+
///
96+
/// \param[in] args the arguments for each axis
97+
/// \sa RHist::Fill(const A &...args)
98+
template <typename... A>
99+
void Fill(const A &...args)
100+
{
101+
fHist->fEngine.FillAtomic(args...);
102+
fStats.Fill(args...);
103+
}
104+
105+
/// Flush locally accumulated entries to the histogram.
106+
void Flush()
107+
{
108+
fHist->fStats.AddAtomic(fStats);
109+
fStats.Clear();
110+
}
111+
};
112+
113+
} // namespace Experimental
114+
} // namespace ROOT
115+
116+
#endif

hist/histv7/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ HIST_ADD_GTEST(hist_atomic hist_atomic.cxx)
22
HIST_ADD_GTEST(hist_auto hist_auto.cxx)
33
HIST_ADD_GTEST(hist_axes hist_axes.cxx)
44
HIST_ADD_GTEST(hist_categorical hist_categorical.cxx)
5+
HIST_ADD_GTEST(hist_concurrent hist_concurrent.cxx)
56
HIST_ADD_GTEST(hist_engine hist_engine.cxx)
67
HIST_ADD_GTEST(hist_engine_atomic hist_engine_atomic.cxx)
78
HIST_ADD_GTEST(hist_hist hist_hist.cxx)

0 commit comments

Comments
 (0)