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