Project
Loading...
Searching...
No Matches
RepresentationBench.h
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
13
41
42#ifndef ALICEO2_BASE_REPRESENTATIONBENCH_H_
43#define ALICEO2_BASE_REPRESENTATIONBENCH_H_
44
46
47#include "TGeoBBox.h"
48#include "TGeoBoolNode.h"
49#include "TGeoCompositeShape.h"
50#include "TGeoMatrix.h"
51#include "TGeoShape.h"
52#include "TGeoTube.h"
53
54#include <algorithm>
55#include <array>
56#include <chrono>
57#include <cmath>
58#include <cstdint>
59#include <cstdio>
60#include <cstring>
61#include <fstream>
62#include <string>
63#include <vector>
64
65#ifdef __linux__
66#include <malloc.h>
67#include <unistd.h>
68#endif
69
70namespace o2
71{
72namespace cad
73{
74namespace bench
75{
76
79
80// ------------------------------------------------------------------------------------------
81// 1. Timing: several passes, a robust statistic, and the spread
82// ------------------------------------------------------------------------------------------
83
89struct TimingStat {
90 long long callsPerPass = 0;
91 int passes = 0;
92 double medianNsPerCall = 0.;
93 double minNsPerCall = 0.;
94 double maxNsPerCall = 0.;
97 double spread = 0.;
102 double hitFraction = -1.;
103};
104
105namespace detail
106{
109inline uint64_t mix(uint64_t acc, double value)
110{
111 uint64_t bits = 0;
112 std::memcpy(&bits, &value, sizeof(bits));
113 acc ^= bits + 0x9e3779b97f4a7c15ULL + (acc << 6) + (acc >> 2);
114 return acc;
115}
116} // namespace detail
117
125template <typename Pass>
126TimingStat timePasses(long long callsPerPass, int warmupPasses, int passes, Pass&& pass)
127{
128 TimingStat stat;
129 stat.callsPerPass = callsPerPass;
130 if (callsPerPass <= 0 || passes <= 0) {
131 return stat;
132 }
133 for (int i = 0; i < warmupPasses; ++i) {
134 stat.checksum = detail::mix(stat.checksum, static_cast<double>(pass()));
135 }
136 std::vector<double> perPass;
137 perPass.reserve(passes);
138 for (int i = 0; i < passes; ++i) {
139 const auto t0 = std::chrono::steady_clock::now();
140 const uint64_t sum = pass();
141 const auto t1 = std::chrono::steady_clock::now();
142 stat.checksum = detail::mix(stat.checksum, static_cast<double>(sum));
143 perPass.push_back(std::chrono::duration<double, std::nano>(t1 - t0).count() /
144 static_cast<double>(callsPerPass));
145 }
146 std::sort(perPass.begin(), perPass.end());
147 stat.passes = passes;
148 stat.minNsPerCall = perPass.front();
149 stat.maxNsPerCall = perPass.back();
150 stat.medianNsPerCall = perPass[perPass.size() / 2];
151 stat.spread = stat.medianNsPerCall > 0.
152 ? (stat.maxNsPerCall - stat.minNsPerCall) / stat.medianNsPerCall
153 : 0.;
154 return stat;
155}
156
157// ------------------------------------------------------------------------------------------
158// 2. Memory: one exact number and one measured number
159// ------------------------------------------------------------------------------------------
160
176 long long residentBytes = 0;
177 long long heapInUseBytes = 0;
178};
179
181{
182 MemorySnapshot out;
183#ifdef __linux__
184 std::ifstream statm("/proc/self/statm");
185 if (statm) {
186 long long totalPages = 0;
187 long long residentPages = 0;
188 statm >> totalPages >> residentPages;
189 out.residentBytes = residentPages * static_cast<long long>(::sysconf(_SC_PAGESIZE));
190 }
191#if defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 33))
192 const struct mallinfo2 info = ::mallinfo2();
193 out.heapInUseBytes = static_cast<long long>(info.uordblks) + static_cast<long long>(info.hblkhd);
194#endif
195#endif
196 return out;
197}
198
200{
201 return {a.residentBytes - b.residentBytes, a.heapInUseBytes - b.heapInUseBytes};
202}
203
210 long long primitives = 0;
211 long long bytes = 0;
212 long long sidecarBytes = 0;
213 std::string formula;
214};
215
218inline long long fileBytes(const std::string& path)
219{
220 std::ifstream in(path, std::ios::binary | std::ios::ate);
221 return in ? static_cast<long long>(in.tellg()) : 0;
222}
223
224// ------------------------------------------------------------------------------------------
225// 3. The sample sets -- built once per part, handed unchanged to every representation
226// ------------------------------------------------------------------------------------------
227
234 std::string partitionedBy;
235 std::vector<Point3D> points;
236 std::vector<char> pointIsInside;
237 std::vector<Ray> outsideRays;
238 std::vector<Ray> insideRays;
239 long long insidePoints = 0;
240};
241
242namespace detail
243{
247struct Lcg {
248 uint64_t state = 88172645463325252ULL;
249 double next()
250 {
251 state = state * 6364136223846793005ULL + 1442695040888963407ULL;
252 return static_cast<double>((state >> 11) & ((1ULL << 53) - 1)) / static_cast<double>(1ULL << 53);
253 }
254};
255} // namespace detail
256
266inline QuerySamples buildQuerySamples(const TGeoShape* reference, const std::string& referenceName,
267 const Point3D& bboxMin, const Point3D& bboxMax, int nPoints,
268 int nRays, uint64_t seed = 20260802ULL, double inflate = 0.12)
269{
270 QuerySamples out;
271 out.partitionedBy = referenceName;
272 detail::Lcg rng{seed};
273 Point3D lo{};
274 Point3D hi{};
275 Point3D centre{};
276 for (int k = 0; k < 3; ++k) {
277 const double half = 0.5 * (bboxMax[k] - bboxMin[k]);
278 centre[k] = 0.5 * (bboxMax[k] + bboxMin[k]);
279 lo[k] = centre[k] - half * (1. + inflate);
280 hi[k] = centre[k] + half * (1. + inflate);
281 }
282 auto drawPoint = [&]() {
283 Point3D p{};
284 for (int k = 0; k < 3; ++k) {
285 p[k] = lo[k] + (hi[k] - lo[k]) * rng.next();
286 }
287 return p;
288 };
289 out.points.reserve(nPoints);
290 out.pointIsInside.reserve(nPoints);
291 for (int i = 0; i < nPoints; ++i) {
292 const Point3D p = drawPoint();
293 const bool in = reference->Contains(p.data());
294 out.points.push_back(p);
295 out.pointIsInside.push_back(in ? 1 : 0);
296 out.insidePoints += in ? 1 : 0;
297 }
298 const int budget = 400 * std::max(1, nRays);
299 int attempts = 0;
300 while (static_cast<int>(out.outsideRays.size()) < nRays && attempts < budget) {
301 ++attempts;
302 const Point3D p = drawPoint();
303 if (reference->Contains(p.data())) {
304 continue;
305 }
306 // Aim at a random point of the (uninflated) bounding box: a thin part is missed by an
307 // isotropic direction almost always, and a DistFromOutside timing dominated by misses prices
308 // the early-out rather than the kernel.
309 Point3D target{};
310 for (int k = 0; k < 3; ++k) {
311 target[k] = bboxMin[k] + (bboxMax[k] - bboxMin[k]) * rng.next();
312 }
313 Point3D d{target[0] - p[0], target[1] - p[1], target[2] - p[2]};
314 const double norm = std::sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
315 if (!(norm > 0.)) {
316 continue;
317 }
318 for (int k = 0; k < 3; ++k) {
319 d[k] /= norm;
320 }
321 out.outsideRays.push_back({p, d});
322 }
323 attempts = 0;
324 while (static_cast<int>(out.insideRays.size()) < nRays && attempts < budget) {
325 ++attempts;
326 const Point3D p = drawPoint();
327 if (!reference->Contains(p.data())) {
328 continue;
329 }
330 const double z = 2. * rng.next() - 1.;
331 const double phi = 2. * 3.14159265358979323846 * rng.next();
332 const double r = std::sqrt(std::max(0., 1. - z * z));
333 out.insideRays.push_back({p, {r * std::cos(phi), r * std::sin(phi), z}});
334 }
335 return out;
336}
337
338// ------------------------------------------------------------------------------------------
339// 4. The four kernel passes
340// ------------------------------------------------------------------------------------------
341//
342// Each is a closure over the shared sample set that performs exactly `callsPerPass` virtual calls
343// and returns a checksum. They exist as named functions rather than as lambdas at the call site
344// so that the unit tests time the same loop bodies the benchmark does.
345
346inline TimingStat timeContainsPass(const TGeoShape* shape, const QuerySamples& s, int warmup, int passes)
347{
348 return timePasses(static_cast<long long>(s.points.size()), warmup, passes, [&]() {
349 uint64_t acc = 0;
350 for (const auto& p : s.points) {
351 acc = detail::mix(acc, shape->Contains(p.data()) ? 1. : 0.);
352 }
353 return acc;
354 });
355}
356
362inline TimingStat timeSafetyPass(const TGeoShape* shape, const QuerySamples& s, int warmup, int passes)
363{
364 return timePasses(static_cast<long long>(s.points.size()), warmup, passes, [&]() {
365 uint64_t acc = 0;
366 for (size_t i = 0; i < s.points.size(); ++i) {
367 acc = detail::mix(acc, shape->Safety(s.points[i].data(), s.pointIsInside[i] ? kTRUE : kFALSE));
368 }
369 return acc;
370 });
371}
372
373inline TimingStat timeDistOutPass(const TGeoShape* shape, const QuerySamples& s, int warmup, int passes)
374{
375 long long hits = 0;
376 long long calls = 0;
377 TimingStat stat = timePasses(static_cast<long long>(s.outsideRays.size()), warmup, passes, [&]() {
378 uint64_t acc = 0;
379 for (const auto& ray : s.outsideRays) {
380 const double d = shape->DistFromOutside(ray.origin.data(), ray.dir.data(), 3, TGeoShape::Big(), nullptr);
381 hits += (d < TGeoShape::Big()) ? 1 : 0;
382 ++calls;
383 acc = detail::mix(acc, d);
384 }
385 return acc;
386 });
387 stat.hitFraction = calls > 0 ? static_cast<double>(hits) / static_cast<double>(calls) : -1.;
388 return stat;
389}
390
391inline TimingStat timeDistInPass(const TGeoShape* shape, const QuerySamples& s, int warmup, int passes)
392{
393 long long hits = 0;
394 long long calls = 0;
395 TimingStat stat = timePasses(static_cast<long long>(s.insideRays.size()), warmup, passes, [&]() {
396 uint64_t acc = 0;
397 for (const auto& ray : s.insideRays) {
398 const double d = shape->DistFromInside(ray.origin.data(), ray.dir.data(), 3, TGeoShape::Big(), nullptr);
399 hits += (d < TGeoShape::Big()) ? 1 : 0;
400 ++calls;
401 acc = detail::mix(acc, d);
402 }
403 return acc;
404 });
405 stat.hitFraction = calls > 0 ? static_cast<double>(hits) / static_cast<double>(calls) : -1.;
406 return stat;
407}
408
409// ------------------------------------------------------------------------------------------
410// 5. The synthetic boolean ladder
411// ------------------------------------------------------------------------------------------
412//
413// Every genuine boolean in the corpus today is a 2-leaf union of two TGeoTubes, so the corpus
414// cannot say how a composite scales with leaf count
415// and no amount of running it harder will make it. This builds the missing fixture: unions of
416// 2, 4, 8, ... TGeoTubes, in the two tree shapes an emitter can plausibly produce.
417//
418// The two shapes are the point of the experiment.
419// * CHAIN -- (((t0 + t1) + t2) + t3) ... : depth K-1, the natural output of a fold over a
420// list of leaves. Every query descends the whole spine.
421// * BALANCED-- a complete binary tree of depth ceil(log2 K). This is what a BVH over primitives
422// would give you for free, minus the bounding-box rejection.
423// If the two scale the same way, tree shape is not where the cost is and a BVH-over-primitives
424// CSG solid has nothing to win from restructuring alone. If they separate, the gap IS the prize.
425
426enum class LadderShape { Chain,
427 Balanced };
428
437inline TGeoShape* buildBooleanLadder(int leaves, LadderShape shape, const std::string& tag)
438{
439 if (leaves < 1) {
440 return nullptr;
441 }
442 const double rMin = 0.2;
443 const double rMax = 0.5;
444 const double dz = 1.0;
445 const double pitch = 0.8;
446 auto leafName = [&](int i) { return tag + "_leaf" + std::to_string(i); };
447 std::vector<TGeoShape*> nodes;
448 std::vector<TGeoMatrix*> offsets;
449 for (int i = 0; i < leaves; ++i) {
450 auto* tube = new TGeoTube(leafName(i).c_str(), rMin, rMax, dz);
451 nodes.push_back(tube);
452 auto* m = new TGeoTranslation((i - 0.5 * (leaves - 1)) * pitch, 0., 0.);
453 m->SetName((leafName(i) + "_m").c_str());
454 m->RegisterYourself();
455 offsets.push_back(m);
456 }
457 if (leaves == 1) {
458 return nodes.front();
459 }
460 int serial = 0;
461 auto join = [&](TGeoShape* a, TGeoMatrix* ma, TGeoShape* b, TGeoMatrix* mb) -> TGeoShape* {
462 auto* node = new TGeoUnion(a, b, ma, mb);
463 auto* composite = new TGeoCompositeShape((tag + "_u" + std::to_string(serial++)).c_str(), node);
464 return composite;
465 };
466 if (shape == LadderShape::Chain) {
467 TGeoShape* acc = nodes[0];
468 TGeoMatrix* accMatrix = offsets[0];
469 for (int i = 1; i < leaves; ++i) {
470 acc = join(acc, accMatrix, nodes[i], offsets[i]);
471 accMatrix = nullptr; // the accumulated composite is already in the common frame
472 }
473 return acc;
474 }
475 std::vector<TGeoShape*> level = nodes;
476 std::vector<TGeoMatrix*> levelMatrix = offsets;
477 while (level.size() > 1) {
478 std::vector<TGeoShape*> next;
479 std::vector<TGeoMatrix*> nextMatrix;
480 for (size_t i = 0; i < level.size(); i += 2) {
481 if (i + 1 < level.size()) {
482 next.push_back(join(level[i], levelMatrix[i], level[i + 1], levelMatrix[i + 1]));
483 nextMatrix.push_back(nullptr);
484 } else {
485 next.push_back(level[i]);
486 nextMatrix.push_back(levelMatrix[i]);
487 }
488 }
489 level.swap(next);
490 levelMatrix.swap(nextMatrix);
491 }
492 return level.front();
493}
494
500 long long leaves = 0;
501 long long nodes = 0;
502 int depth = 0;
503};
504
505inline BooleanTreeStats booleanTreeStats(const TGeoShape* shape)
506{
508 const auto* composite = dynamic_cast<const TGeoCompositeShape*>(shape);
509 if (composite == nullptr || composite->GetBoolNode() == nullptr) {
510 out.leaves = 1;
511 out.depth = 1;
512 return out;
513 }
514 const TGeoBoolNode* node = composite->GetBoolNode();
515 const BooleanTreeStats left = booleanTreeStats(node->GetLeftShape());
516 const BooleanTreeStats right = booleanTreeStats(node->GetRightShape());
517 out.leaves = left.leaves + right.leaves;
518 out.nodes = left.nodes + right.nodes + 1;
519 out.depth = 1 + std::max(left.depth, right.depth);
520 return out;
521}
522
523// ------------------------------------------------------------------------------------------
524// 6. The negative control for the timing harness itself
525// ------------------------------------------------------------------------------------------
526
534class BallastShape : public TGeoBBox
535{
536 public:
537 BallastShape(const char* name, double dx, double dy, double dz, int burn)
538 : TGeoBBox(name, dx, dy, dz), mBurn(burn) {}
539
540 double ballast(const double* point) const
541 {
542 double acc = 1.;
543 for (int i = 0; i < mBurn; ++i) {
544 acc = std::sqrt(acc * acc + point[i % 3] * point[i % 3] + 1.);
545 }
546 return acc;
547 }
548
549 bool Contains(const double* point) const override
550 {
551 return TGeoBBox::Contains(point) && ballast(point) > 0.;
552 }
553 double Safety(const double* point, bool in = kTRUE) const override
554 {
555 return TGeoBBox::Safety(point, in) + 0. * ballast(point);
556 }
557 double DistFromOutside(const double* point, const double* dir, int iact = 1,
558 double step = TGeoShape::Big(), double* safe = nullptr) const override
559 {
560 return TGeoBBox::DistFromOutside(point, dir, iact, step, safe) + 0. * ballast(point);
561 }
562 double DistFromInside(const double* point, const double* dir, int iact = 1,
563 double step = TGeoShape::Big(), double* safe = nullptr) const override
564 {
565 return TGeoBBox::DistFromInside(point, dir, iact, step, safe) + 0. * ballast(point);
566 }
567
568 private:
569 int mBurn = 0;
570};
571
572} // namespace bench
573} // namespace cad
574} // namespace o2
575
576#endif
std::unique_ptr< expressions::Node > node
std::vector< double > sum
int32_t i
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error s
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events Show standalone event Run event generator maximum time bin of continuous for triggered for automatic continuous for automatic continuous triggered uint64_t
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events d
Validation and timing harness for TGeoShape navigation, typed on plain TGeoShape*.
bool Contains(const double *point) const override
BallastShape(const char *name, double dx, double dy, double dz, int burn)
double ballast(const double *point) const
double DistFromInside(const double *point, const double *dir, int iact=1, double step=TGeoShape::Big(), double *safe=nullptr) const override
double Safety(const double *point, bool in=kTRUE) const override
double DistFromOutside(const double *point, const double *dir, int iact=1, double step=TGeoShape::Big(), double *safe=nullptr) const override
const GLfloat * m
Definition glcorearb.h:4066
GLint GLsizei count
Definition glcorearb.h:399
GLuint GLsizei const GLuint const GLintptr * offsets
Definition glcorearb.h:2595
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble GLdouble right
Definition glcorearb.h:4077
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint reference
Definition glcorearb.h:5487
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLint left
Definition glcorearb.h:1979
GLenum GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const void * bits
Definition glcorearb.h:4150
GLint GLint GLsizei GLsizei GLsizei depth
Definition glcorearb.h:470
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLint level
Definition glcorearb.h:275
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t0
Definition glcorearb.h:5034
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat t1
Definition glcorearb.h:5034
uint64_t mix(uint64_t acc, double value)
TimingStat timeSafetyPass(const TGeoShape *shape, const QuerySamples &s, int warmup, int passes)
long long fileBytes(const std::string &path)
QuerySamples buildQuerySamples(const TGeoShape *reference, const std::string &referenceName, const Point3D &bboxMin, const Point3D &bboxMax, int nPoints, int nRays, uint64_t seed=20260802ULL, double inflate=0.12)
TGeoShape * buildBooleanLadder(int leaves, LadderShape shape, const std::string &tag)
TimingStat timeContainsPass(const TGeoShape *shape, const QuerySamples &s, int warmup, int passes)
MemorySnapshot readMemory()
MemorySnapshot operator-(const MemorySnapshot &a, const MemorySnapshot &b)
TimingStat timePasses(long long callsPerPass, int warmupPasses, int passes, Pass &&pass)
TimingStat timeDistInPass(const TGeoShape *shape, const QuerySamples &s, int warmup, int passes)
TimingStat timeDistOutPass(const TGeoShape *shape, const QuerySamples &s, int warmup, int passes)
BooleanTreeStats booleanTreeStats(const TGeoShape *shape)
std::array< double, 3 > Point3D
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
long long nodes
TGeoCompositeShape / TGeoBoolNode pairs.
std::vector< char > pointIsInside
the reference's Contains() for each, as a fixed label
std::vector< Ray > insideRays
origin inside per the reference, isotropic direction
std::vector< Ray > outsideRays
origin outside per the reference, aimed into the bbox
std::vector< Point3D > points
all query points, mixed inside/outside, in bbox order
long long primitives
triangles / analytic patches / boolean leaves
long long sidecarBytes
the file the representation was loaded from, on disk
long long bytes
the arithmetic below, exact for the arrays it counts