Skip to content

Commit 07cf897

Browse files
committed
[hist] Support snapshot with user-defined types
1 parent f5ca8b3 commit 07cf897

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

hist/histv7/inc/ROOT/RHistUtils.hxx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ std::enable_if_t<std::is_arithmetic_v<T>> AtomicLoad(const T *ptr, T *ret)
149149
#endif
150150
}
151151

152+
template <typename T>
153+
auto AtomicLoad(const T *ptr, T *ret) -> decltype(ptr->AtomicLoad(ret))
154+
{
155+
return ptr->AtomicLoad(ret);
156+
}
157+
152158
template <typename T>
153159
std::enable_if_t<std::is_arithmetic_v<T>> AtomicLoadAcquire(const T *ptr, T *ret)
154160
{

hist/histv7/test/hist_engine_atomic.cxx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,61 @@ TEST(RHistEngine_RBinWithError, StressFillAtomicWeight)
441441
EXPECT_EQ(engine.GetBinContent(0).fSum, NFills * Weight);
442442
EXPECT_EQ(engine.GetBinContent(0).fSum2, NFills * Weight * Weight);
443443
}
444+
445+
TEST(RHistEngine_RBinWithError, SnapshotAtomic)
446+
{
447+
static constexpr std::size_t Bins = 20;
448+
const RRegularAxis axis(Bins, {0, Bins});
449+
RHistEngine<RBinWithError> engineA({axis});
450+
451+
engineA.Fill(-100);
452+
for (std::size_t i = 0; i < Bins; i++) {
453+
engineA.Fill(i + 0.5);
454+
}
455+
engineA.Fill(100);
456+
457+
RHistEngine<RBinWithError> engineB = engineA.SnapshotAtomic();
458+
ASSERT_EQ(engineB.GetNDimensions(), 1);
459+
ASSERT_EQ(engineB.GetTotalNBins(), Bins + 2);
460+
461+
EXPECT_EQ(engineB.GetBinContent(RBinIndex::Underflow()).fSum, 1);
462+
for (auto index : axis.GetNormalRange()) {
463+
EXPECT_EQ(engineB.GetBinContent(index).fSum, 1);
464+
}
465+
EXPECT_EQ(engineB.GetBinContent(RBinIndex::Overflow()).fSum, 1);
466+
}
467+
468+
TEST(RHistEngine_RBinWithError, StressSnapshotAtomic)
469+
{
470+
static constexpr std::size_t Bins = 20;
471+
static constexpr std::size_t NThreads = 4;
472+
static constexpr std::size_t NFillsPerThread = 100000;
473+
static constexpr std::size_t NSnapshots = 10000;
474+
475+
// Create a histogram with some bins that takes a bit of time to snapshot. The idea of this stress test is then to
476+
// fill the first and last bin from multiple threads. If the snapshot is consistent, it must never have a bigger bin
477+
// content in the last bin.
478+
RHistEngine<RBinWithError> engine(Bins, {0, Bins});
479+
double first = 0, last = 0;
480+
481+
std::atomic_flag snapshotter;
482+
StressInParallel(NThreads, [&] {
483+
if (!snapshotter.test_and_set()) {
484+
for (std::size_t i = 0; i < NSnapshots; i++) {
485+
RHistEngine<RBinWithError> snapshot = engine.SnapshotAtomic();
486+
first = snapshot.GetBinContent(0).fSum;
487+
last = snapshot.GetBinContent(Bins - 1).fSum;
488+
if (last > first) {
489+
return;
490+
}
491+
}
492+
} else {
493+
for (std::size_t i = 0; i < NFillsPerThread; i++) {
494+
engine.FillAtomic(0.5);
495+
engine.FillAtomic(Bins - 0.5);
496+
}
497+
}
498+
});
499+
500+
EXPECT_GE(first, last);
501+
}

hist/histv7/test/hist_user.cxx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ struct User {
6161
void AtomicAdd(const UserWeight &w) { ROOT::Experimental::Internal::AtomicAdd(&fValue, w.fWeight); }
6262

6363
void AtomicAdd(const User &rhs) { ROOT::Experimental::Internal::AtomicAdd(&fValue, rhs.fValue); }
64+
65+
void AtomicLoad(User *ret) const { ROOT::Experimental::Internal::AtomicLoad(&fValue, &ret->fValue); }
6466
};
6567

6668
static_assert(std::is_nothrow_move_constructible_v<RHistEngine<User>>);
@@ -274,3 +276,16 @@ TEST(RHistEngineUser, SetBinContent)
274276
engine.SetBinContent(indices, 43);
275277
EXPECT_EQ(engine.GetBinContent(indices).fValue, 43);
276278
}
279+
280+
TEST(RHistEngineUser, SnapshotAtomic)
281+
{
282+
// Snapshotting uses AtomicLoad.
283+
static constexpr std::size_t Bins = 20;
284+
const RRegularAxis axis(Bins, {0, Bins});
285+
RHistEngine<User> engineA({axis});
286+
287+
engineA.Fill(8.5);
288+
289+
RHistEngine<User> engineB = engineA.SnapshotAtomic();
290+
EXPECT_EQ(engineB.GetBinContent(8).fValue, 1);
291+
}

0 commit comments

Comments
 (0)