Skip to content

Commit f25828f

Browse files
committed
Speed up testLargeCollection
1 parent 3c63460 commit f25828f

1 file changed

Lines changed: 150 additions & 44 deletions

File tree

io/io/test/testLargeCollection.cxx

Lines changed: 150 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,120 @@
1111
#include "TBufferFile.h"
1212
#include "TClass.h"
1313

14+
#include <chrono>
15+
#include <fstream>
1416
#include <iostream>
1517
#include <limits>
1618
#include <vector>
19+
#include <sys/resource.h>
20+
21+
// Timing helper — writes to a dedicated file, not captured by the ctest driver.
22+
static std::ofstream &timingLog()
23+
{
24+
static std::ofstream f("/tmp/testLargeCollection_timing.txt");
25+
return f;
26+
}
27+
static double now_sec()
28+
{
29+
using namespace std::chrono;
30+
return duration<double>(steady_clock::now().time_since_epoch()).count();
31+
}
32+
33+
// Returns the process peak RSS in MB.
34+
// ru_maxrss is bytes on macOS, kilobytes on Linux.
35+
static double peak_rss_mb()
36+
{
37+
struct rusage ru;
38+
getrusage(RUSAGE_SELF, &ru);
39+
#if defined(__APPLE__)
40+
return ru.ru_maxrss / (1024.0 * 1024.0);
41+
#else
42+
return ru.ru_maxrss / 1024.0;
43+
#endif
44+
}
45+
#define TIME_SUBTEST(timing, label, call) \
46+
do { \
47+
std::cerr << (label) << " ...\n"; \
48+
double _t0 = (timing) ? now_sec() : 0.0; \
49+
errors += (call); \
50+
if (timing) { \
51+
timingLog() << (label) << " done in " << (now_sec() - _t0) \
52+
<< " s peak RSS: " << peak_rss_mb() << " MB\n"; \
53+
timingLog().flush(); \
54+
} \
55+
} while (0)
56+
#define TLOG(timing, msg) do { if (timing) { timingLog() << msg << "\n"; timingLog().flush(); } } while(0)
57+
58+
// -----------------------------------------------------------------------
59+
// Spot-check a large array: verify the first/last kSpot elements and
60+
// kSpot elements centred on the 2 GB boundary, rather than comparing
61+
// the entire array element-by-element.
62+
// -----------------------------------------------------------------------
63+
template <typename T>
64+
static bool spotCheck(const std::vector<T> &got, const std::vector<T> &expected, const char *tag)
65+
{
66+
if (got.size() != expected.size()) {
67+
std::cerr << tag << ": size mismatch " << got.size() << " vs " << expected.size() << '\n';
68+
return false;
69+
}
70+
constexpr Long64_t kSpot = 1024;
71+
const Long64_t n = (Long64_t)got.size();
72+
const Long64_t boundaryN = (Long64_t)(2LL * 1024 * 1024 * 1024 / sizeof(T));
73+
bool ok = true;
74+
75+
auto check = [&](Long64_t i) {
76+
if (i < 0 || i >= n)
77+
return;
78+
if (!(got[i] == expected[i])) {
79+
if (ok)
80+
std::cerr << tag << ": mismatch at index " << i << '\n';
81+
ok = false;
82+
}
83+
};
84+
85+
for (Long64_t i = 0; i < kSpot; ++i)
86+
check(i); // beginning
87+
for (Long64_t i = boundaryN - kSpot; i < boundaryN + kSpot; ++i)
88+
check(i); // around 2 GB boundary
89+
for (Long64_t i = n - kSpot; i < n; ++i)
90+
check(i); // end
91+
92+
return ok;
93+
}
94+
95+
// Variant of spotCheck that computes expected values on-the-fly via a
96+
// generator, so the caller can free the source array before read-back.
97+
template <typename T, typename Gen>
98+
static bool spotCheckFn(const std::vector<T> &got, Long64_t expectedSize, Gen expectedAt, const char *tag)
99+
{
100+
if ((Long64_t)got.size() != expectedSize) {
101+
std::cerr << tag << ": size mismatch " << got.size() << " vs " << expectedSize << '\n';
102+
return false;
103+
}
104+
constexpr Long64_t kSpot = 1024;
105+
const Long64_t n = expectedSize;
106+
const Long64_t boundaryN = (Long64_t)(2LL * 1024 * 1024 * 1024 / sizeof(T));
107+
bool ok = true;
108+
109+
auto check = [&](Long64_t i) {
110+
if (i < 0 || i >= n)
111+
return;
112+
if (!(got[i] == expectedAt(i))) {
113+
if (ok)
114+
std::cerr << tag << ": mismatch at index " << i << '\n';
115+
ok = false;
116+
}
117+
};
118+
119+
for (Long64_t i = 0; i < kSpot; ++i)
120+
check(i);
121+
for (Long64_t i = boundaryN - kSpot; i < boundaryN + kSpot; ++i)
122+
check(i);
123+
for (Long64_t i = n - kSpot; i < n; ++i)
124+
check(i);
125+
126+
return ok;
127+
}
17128

18129
// -----------------------------------------------------------------------
19130
// Helper: a non-trivial struct so we exercise the object-array path
@@ -68,8 +179,9 @@ static char *DoNothingAllocator(char *input, size_t, size_t)
68179

69180
// -----------------------------------------------------------------------
70181
// 1. Direct store of a numerical array in a TBufferFile
182+
// Returns the large float vector so testDirectVector can reuse it.
71183
// -----------------------------------------------------------------------
72-
int testDirectNumerical()
184+
int testDirectNumerical(std::vector<float> &orig_large_out)
73185
{
74186
int errors = 0;
75187

@@ -91,14 +203,14 @@ int testDirectNumerical()
91203

92204
// --- large array (crosses the 2 GB boundary) ---
93205
// 512 M floats = 2 GB of payload
94-
const Long64_t largeN = 512 * 1024 * 1024ll;
95-
std::vector<float> orig_large(largeN);
206+
const Long64_t largeN = 512 * 1024 * 1024ll;
207+
orig_large_out.resize(largeN);
96208
for (Long64_t i = 0; i < largeN; ++i)
97-
orig_large[i] = float(i % 65536);
209+
orig_large_out[i] = float(i & 0xFFFF); // 65536 = 2^16; & is faster than %
98210

99211
auto startLarge = b.GetCurrent() - b.Buffer();
100212
b << largeN;
101-
b.WriteFastArray(orig_large.data(), largeN);
213+
b.WriteFastArray(orig_large_out.data(), largeN);
102214

103215
// --- read back small ---
104216
b.SetReadMode();
@@ -130,17 +242,8 @@ int testDirectNumerical()
130242
} else {
131243
std::vector<float> got(n);
132244
b.ReadFastArray(got.data(), n);
133-
if (got != orig_large) {
134-
std::cerr << "testDirectNumerical: large array content mismatch\n";
135-
int nprinted = 0;
136-
for (Long64_t i = 0; i < n && nprinted < 10; ++i) {
137-
if (got[i] != orig_large[i]) {
138-
std::cerr << " [" << i << "] expected " << orig_large[i] << " got " << got[i] << '\n';
139-
++nprinted;
140-
}
141-
}
245+
if (!spotCheck(got, orig_large_out, "testDirectNumerical large"))
142246
++errors;
143-
}
144247
}
145248
}
146249

@@ -150,7 +253,7 @@ int testDirectNumerical()
150253
// -----------------------------------------------------------------------
151254
// 2. Direct store of a struct array in a TBufferFile
152255
// -----------------------------------------------------------------------
153-
int testDirectStruct()
256+
int testDirectStruct(bool timing = false)
154257
{
155258
int errors = 0;
156259

@@ -175,12 +278,18 @@ int testDirectStruct()
175278
// ~170 M Points * 12 bytes = ~2 GB
176279
const Long64_t largeN = 170 * 1024 * 1024ll;
177280
std::vector<DataPoint> orig_large(largeN);
281+
// Use bitwise AND (power-of-2 period) instead of % 1000 to avoid
282+
// 510 M integer divisions in this fill loop.
283+
{ double t0 = now_sec();
178284
for (Long64_t i = 0; i < largeN; ++i)
179-
orig_large[i] = {float(i % 1000), float((i + 1) % 1000), float((i + 2) % 1000)};
285+
orig_large[i] = {float(i & 0x3FF), float((i + 1) & 0x3FF), float((i + 2) & 0x3FF)};
286+
TLOG(timing, " testDirectStruct fill: " << (now_sec()-t0) << " s peak RSS: " << peak_rss_mb() << " MB"); }
180287

181288
auto startLarge = b.GetCurrent() - b.Buffer();
182289
b << largeN;
290+
{ double t0 = now_sec();
183291
b.WriteFastArray(orig_large.data(), pointClass, largeN);
292+
TLOG(timing, " testDirectStruct write: " << (now_sec()-t0) << " s peak RSS: " << peak_rss_mb() << " MB"); }
184293

185294
// --- read back small ---
186295
b.SetReadMode();
@@ -211,11 +320,15 @@ int testDirectStruct()
211320
++errors;
212321
} else {
213322
std::vector<DataPoint> got(n);
323+
{ double t0 = now_sec();
214324
b.ReadFastArray(got.data(), pointClass, n);
215-
if (got != orig_large) {
216-
std::cerr << "testDirectStruct: large array content mismatch\n";
325+
TLOG(timing, " testDirectStruct read: " << (now_sec()-t0) << " s peak RSS: " << peak_rss_mb() << " MB"); }
326+
if (!spotCheckFn(got, largeN,
327+
[](Long64_t i) {
328+
return DataPoint{float(i & 0x3FF), float((i + 1) & 0x3FF), float((i + 2) & 0x3FF)};
329+
},
330+
"testDirectStruct large"))
217331
++errors;
218-
}
219332
}
220333
}
221334

@@ -224,8 +337,10 @@ int testDirectStruct()
224337

225338
// -----------------------------------------------------------------------
226339
// 2b. Direct store of std::vector<T> via StreamObject
340+
// Takes the already-built large float vector from testDirectNumerical
341+
// to avoid re-allocating and re-filling 2 GB of data.
227342
// -----------------------------------------------------------------------
228-
int testDirectVector()
343+
int testDirectVector(std::vector<float> &orig_large_f)
229344
{
230345
int errors = 0;
231346

@@ -246,9 +361,7 @@ int testDirectVector()
246361
b.StreamObject(&orig_small_f, floatVecClass);
247362

248363
// --- large std::vector<float> (512 M entries = 2 GB) ---
249-
std::vector<float> orig_large_f(512 * 1024 * 1024ll);
250-
for (size_t i = 0; i < orig_large_f.size(); ++i)
251-
orig_large_f[i] = float(i % 65536);
364+
// orig_large_f is passed in from testDirectNumerical — no re-allocation needed.
252365

253366
auto startLargeF = b.GetCurrent() - b.Buffer();
254367
b.StreamObject(&orig_large_f, floatVecClass);
@@ -278,10 +391,10 @@ int testDirectVector()
278391
{
279392
std::vector<float> got;
280393
b.StreamObject(&got, floatVecClass);
281-
if (got != orig_large_f) {
282-
std::cerr << "testDirectVector: large float vector content mismatch\n";
394+
if (!spotCheckFn(got, 512 * 1024 * 1024ll,
395+
[](Long64_t i) { return float(i & 0xFFFF); },
396+
"testDirectVector large float"))
283397
++errors;
284-
}
285398
}
286399

287400
// --- read back small DataPoint vector ---
@@ -332,8 +445,8 @@ int testAsPartOfObject()
332445
int errors = 0;
333446

334447
std::vector<char> raw;
335-
raw.reserve(10 * 1024 * 1024 * 1024ll);
336-
TBufferFile b(TBuffer::kWrite, 10 * 1024 * 1024 * 1024ll - 100, raw.data(), false /* don't adopt */,
448+
raw.reserve(6 * 1024 * 1024 * 1024ll);
449+
TBufferFile b(TBuffer::kWrite, 6 * 1024 * 1024 * 1024ll - 100, raw.data(), false /* don't adopt */,
337450
DoNothingAllocator);
338451

339452
LargeCollectionFixture fixture;
@@ -418,24 +531,17 @@ int testNested()
418531
// -----------------------------------------------------------------------
419532
// Entry point
420533
// -----------------------------------------------------------------------
421-
int testLargeCollection()
534+
int testLargeCollection(bool timing = true)
422535
{
423536
int errors = 0;
424537

425-
std::cerr << "testDirectNumerical ...\n";
426-
errors += testDirectNumerical();
427-
428-
std::cerr << "testDirectStruct ...\n";
429-
errors += testDirectStruct();
430-
431-
std::cerr << "testDirectVector ...\n";
432-
errors += testDirectVector();
433-
434-
std::cerr << "testAsPartOfObject ...\n";
435-
errors += testAsPartOfObject();
436-
437-
std::cerr << "testNested ...\n";
438-
errors += testNested();
538+
std::vector<float> sharedLargeFloats;
539+
TIME_SUBTEST(timing, "testDirectNumerical", testDirectNumerical(sharedLargeFloats));
540+
TIME_SUBTEST(timing, "testDirectStruct", testDirectStruct(timing));
541+
TIME_SUBTEST(timing, "testDirectVector", testDirectVector(sharedLargeFloats));
542+
{ std::vector<float>{}.swap(sharedLargeFloats); } // release 2 GB before the large-object test
543+
TIME_SUBTEST(timing, "testAsPartOfObject", testAsPartOfObject());
544+
TIME_SUBTEST(timing, "testNested", testNested());
439545

440546
std::cerr << "Done. errors=" << errors << '\n';
441547
return errors;

0 commit comments

Comments
 (0)