Skip to content

Commit f523d62

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 ae5d93d commit f523d62

File tree

9 files changed

+397
-1
lines changed

9 files changed

+397
-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+
To uses 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
@@ -22,6 +22,10 @@ class TBuffer;
2222
namespace ROOT {
2323
namespace Experimental {
2424

25+
// forward declaration for friend declaration
26+
template <typename BinContentType>
27+
class RHistFillContext;
28+
2529
/**
2630
A histogram for aggregation of data along multiple dimensions.
2731
@@ -53,6 +57,8 @@ Feedback is welcome!
5357
*/
5458
template <typename BinContentType>
5559
class RHist final {
60+
friend class RHistFillContext<BinContentType>;
61+
5662
/// The histogram engine including the bin contents.
5763
RHistEngine<BinContentType> fEngine;
5864
/// The global histogram statistics.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
std::lock_guard g(fMutex);
81+
82+
// Cannot use std::make_shared because the constructor of RHistFillContext is private. Also it would mean that the
83+
// (direct) memory of all contexts stays around until the vector of weak_ptr's is cleared.
84+
std::shared_ptr<RHistFillContext<BinContentType>> context(new RHistFillContext<BinContentType>(*fHist));
85+
fFillContexts.push_back(context);
86+
return context;
87+
}
88+
};
89+
90+
} // namespace Experimental
91+
} // namespace ROOT
92+
93+
#endif
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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.GetStats())
40+
{
41+
// The histogram may already have some statistics. Reset them after copying the structure.
42+
fStats.Clear();
43+
}
44+
RHistFillContext(const RHistFillContext<BinContentType> &) = delete;
45+
RHistFillContext(RHistFillContext<BinContentType> &&) = default;
46+
RHistFillContext<BinContentType> &operator=(const RHistFillContext<BinContentType> &) = delete;
47+
RHistFillContext<BinContentType> &operator=(RHistFillContext<BinContentType> &&) = default;
48+
49+
public:
50+
~RHistFillContext() { Flush(); }
51+
52+
/// Fill an entry into the histogram.
53+
///
54+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
55+
/// discarded.
56+
///
57+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
58+
/// converted for the axis type at run-time.
59+
///
60+
/// \param[in] args the arguments for each axis
61+
/// \sa RHist::Fill(const std::tuple<A...> &args)
62+
template <typename... A>
63+
void Fill(const std::tuple<A...> &args)
64+
{
65+
fHist->fEngine.FillAtomic(args);
66+
fStats.Fill(args);
67+
}
68+
69+
/// Fill an entry into the histogram with a weight.
70+
///
71+
/// This overload is not available for integral bin content types (see \ref RHistEngine::SupportsWeightedFilling).
72+
///
73+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
74+
/// discarded.
75+
///
76+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
77+
/// converted for the axis type at run-time.
78+
///
79+
/// \param[in] args the arguments for each axis
80+
/// \param[in] weight the weight for this entry
81+
/// \sa RHist::Fill(const std::tuple<A...> &args, RWeight weight)
82+
template <typename... A>
83+
void Fill(const std::tuple<A...> &args, RWeight weight)
84+
{
85+
fHist->fEngine.FillAtomic(args, weight);
86+
fStats.Fill(args, weight);
87+
}
88+
89+
/// Fill an entry into the histogram.
90+
///
91+
/// For weighted filling, pass an RWeight as the last argument. This is not available for integral bin content types
92+
/// (see \ref RHistEngine::SupportsWeightedFilling).
93+
///
94+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
95+
/// discarded.
96+
///
97+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
98+
/// converted for the axis type at run-time.
99+
///
100+
/// \param[in] args the arguments for each axis
101+
/// \sa RHist::Fill(const A &...args)
102+
template <typename... A>
103+
void Fill(const A &...args)
104+
{
105+
fHist->fEngine.FillAtomic(args...);
106+
fStats.Fill(args...);
107+
}
108+
109+
/// Flush locally accumulated entries to the histogram.
110+
void Flush()
111+
{
112+
fHist->fStats.AddAtomic(fStats);
113+
fStats.Clear();
114+
}
115+
};
116+
117+
} // namespace Experimental
118+
} // namespace ROOT
119+
120+
#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)