Skip to content

Commit 1e7e4d9

Browse files
committed
[hist] Add stress tests for AtomicAdd
1 parent 9ff450d commit 1e7e4d9

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

hist/histv7/test/hist_engine_atomic.cxx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "hist_test.hxx"
22

3+
#include <atomic>
4+
#include <cstddef>
5+
36
TEST(RHistEngine, AddAtomic)
47
{
58
static constexpr std::size_t Bins = 20;
@@ -24,6 +27,27 @@ TEST(RHistEngine, AddAtomic)
2427
EXPECT_EQ(engineA.GetBinContent(RBinIndex::Overflow()), 1);
2528
}
2629

30+
TEST(RHistEngine, StressAddAtomic)
31+
{
32+
static constexpr std::size_t NThreads = 4;
33+
static constexpr std::size_t NAddsPerThread = 10000;
34+
static constexpr std::size_t NAdds = NThreads * NAddsPerThread;
35+
36+
// Fill a single bin, to maximize contention.
37+
const RRegularAxis axis(1, {0, 1});
38+
RHistEngine<int> engineA({axis});
39+
RHistEngine<int> engineB({axis});
40+
engineB.Fill(0.5);
41+
42+
StressInParallel(NThreads, [&] {
43+
for (std::size_t i = 0; i < NAddsPerThread; i++) {
44+
engineA.AddAtomic(engineB);
45+
}
46+
});
47+
48+
EXPECT_EQ(engineA.GetBinContent(0), NAdds);
49+
}
50+
2751
TEST(RHistEngine, AddAtomicDifferent)
2852
{
2953
// The equality operators of RAxes and the axis objects are already unit-tested separately, so here we only check one
@@ -213,6 +237,36 @@ TEST(RHistEngine, FillAtomicTupleWeightInvalidNumberOfArguments)
213237
EXPECT_THROW(engine2.FillAtomic(std::make_tuple(1, 2, 3), RWeight(1)), std::invalid_argument);
214238
}
215239

240+
TEST(RHistEngine, StressFillAddAtomicWeight)
241+
{
242+
static constexpr std::size_t NThreads = 4;
243+
static constexpr std::size_t NOpsPerThread = 10000;
244+
static constexpr std::size_t NOps = NThreads * NOpsPerThread;
245+
static constexpr double Weight = 0.5;
246+
247+
// Fill a single bin, to maximize contention.
248+
const RRegularAxis axis(1, {0, 1});
249+
RHistEngine<float> engineA({axis});
250+
RHistEngine<float> engineB({axis});
251+
engineB.Fill(0.5, RWeight(Weight));
252+
253+
std::atomic<int> op = 0;
254+
StressInParallel(NThreads, [&] {
255+
int chosenOp = op.fetch_add(1) % 2;
256+
if (chosenOp == 0) {
257+
for (std::size_t i = 0; i < NOpsPerThread; i++) {
258+
engineA.FillAtomic(0.5, RWeight(Weight));
259+
}
260+
} else {
261+
for (std::size_t i = 0; i < NOpsPerThread; i++) {
262+
engineA.AddAtomic(engineB);
263+
}
264+
}
265+
});
266+
267+
EXPECT_EQ(engineA.GetBinContent(0), NOps * Weight);
268+
}
269+
216270
TEST(RHistEngine_RBinWithError, AddAtomic)
217271
{
218272
static constexpr std::size_t Bins = 20;
@@ -236,6 +290,28 @@ TEST(RHistEngine_RBinWithError, AddAtomic)
236290
}
237291
}
238292

293+
TEST(RHistEngine_RBinWithError, StressAddAtomic)
294+
{
295+
static constexpr std::size_t NThreads = 4;
296+
static constexpr std::size_t NAddsPerThread = 10000;
297+
static constexpr std::size_t NAdds = NThreads * NAddsPerThread;
298+
299+
// Fill a single bin, to maximize contention.
300+
const RRegularAxis axis(1, {0, 1});
301+
RHistEngine<RBinWithError> engineA({axis});
302+
RHistEngine<RBinWithError> engineB({axis});
303+
engineB.Fill(0.5);
304+
305+
StressInParallel(NThreads, [&] {
306+
for (std::size_t i = 0; i < NAddsPerThread; i++) {
307+
engineA.AddAtomic(engineB);
308+
}
309+
});
310+
311+
EXPECT_EQ(engineA.GetBinContent(0).fSum, NAdds);
312+
EXPECT_EQ(engineA.GetBinContent(0).fSum2, NAdds);
313+
}
314+
239315
TEST(RHistEngine_RBinWithError, FillAtomic)
240316
{
241317
static constexpr std::size_t Bins = 20;

hist/histv7/test/hist_hist.cxx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,28 @@ TEST(RHist, AddAtomic)
6969
EXPECT_EQ(histA.GetBinContent(RBinIndex(9)), 1);
7070
}
7171

72+
TEST(RHist, StressAddAtomic)
73+
{
74+
static constexpr std::size_t NThreads = 4;
75+
static constexpr std::size_t NAddsPerThread = 10000;
76+
static constexpr std::size_t NAdds = NThreads * NAddsPerThread;
77+
78+
// Fill a single bin, to maximize contention.
79+
const RRegularAxis axis(1, {0, 1});
80+
RHist<int> histA({axis});
81+
RHist<int> histB({axis});
82+
histB.Fill(0.5);
83+
84+
StressInParallel(NThreads, [&] {
85+
for (std::size_t i = 0; i < NAddsPerThread; i++) {
86+
histA.AddAtomic(histB);
87+
}
88+
});
89+
90+
EXPECT_EQ(histA.GetNEntries(), NAdds);
91+
EXPECT_EQ(histA.GetBinContent(0), NAdds);
92+
}
93+
7294
TEST(RHist, Clear)
7395
{
7496
static constexpr std::size_t Bins = 20;

hist/histv7/test/hist_stats.cxx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,36 @@ TEST(RHistStats, AddAtomic)
156156
}
157157
}
158158

159+
TEST(RHistStats, StressAddAtomic)
160+
{
161+
static constexpr std::size_t NThreads = 4;
162+
static constexpr std::size_t NAddsPerThread = 10000;
163+
static constexpr std::size_t NAdds = NThreads * NAddsPerThread;
164+
static constexpr double X = 1.5;
165+
static constexpr double Weight = 0.5;
166+
167+
// Use a single dimension, to maximize contention.
168+
RHistStats statsA(1);
169+
RHistStats statsB(1);
170+
statsB.Fill(X, RWeight(Weight));
171+
172+
StressInParallel(NThreads, [&] {
173+
for (std::size_t i = 0; i < NAddsPerThread; i++) {
174+
statsA.AddAtomic(statsB);
175+
}
176+
});
177+
178+
EXPECT_EQ(statsA.GetNEntries(), NAdds);
179+
EXPECT_DOUBLE_EQ(statsA.GetSumW(), NAdds * Weight);
180+
EXPECT_DOUBLE_EQ(statsA.GetSumW2(), NAdds * Weight * Weight);
181+
182+
const auto &dimensionStats = statsA.GetDimensionStats();
183+
EXPECT_DOUBLE_EQ(dimensionStats.fSumWX, NAdds * Weight * X);
184+
EXPECT_DOUBLE_EQ(dimensionStats.fSumWX2, NAdds * Weight * X * X);
185+
EXPECT_DOUBLE_EQ(dimensionStats.fSumWX3, NAdds * Weight * X * X * X);
186+
EXPECT_DOUBLE_EQ(dimensionStats.fSumWX4, NAdds * Weight * X * X * X * X);
187+
}
188+
159189
TEST(RHistStats, AddAtomicDifferent)
160190
{
161191
RHistStats statsA(2);

0 commit comments

Comments
 (0)