Project
Loading...
Searching...
No Matches
GPUCommonDoubleBinary64.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.
11
14
15#ifndef GPUCOMMONDOUBLEBINARY64_H
16#define GPUCOMMONDOUBLEBINARY64_H
17
18#if !defined(__METAL__) && !defined(GPUCA_B64_HOST_REFERENCE)
19#error "the emulated binary64 is for Metal, which has no double of its own; every other backend has a real one. Define GPUCA_B64_HOST_REFERENCE to build it on the host as a test reference."
20#endif
21
22#include "GPUCommonDef.h"
23#ifndef GPUCA_GPUCODE_DEVICE
24#include <cstdint>
25#endif
26
27// IEEE-754 binary64 in software, for a device that has no double at all. Round to
28// nearest even only, with subnormals, infinities and NaNs; NaN propagation follows
29// the ARM64 order, so an Apple host is a bit-exact reference down to the payload.
30// There is no fused multiply-add, no square root and no other rounding mode.
31//
32// Addition, subtraction, multiplication, division and the conversions to and from
33// float and the 32-bit integers are exact, so a Metal build reproduces the CPU
34// result bit for bit wherever it uses only those. sin and cos come from fdlibm and
35// land within 2 ulp of libm rather than matching it.
36//
37// It costs of the order of a hundred times plain float on an M-series GPU, which
38// the tracking can afford because double is a small fraction of its floating point
39// work.
40
41namespace o2::gpu
42{
43
44namespace binary64_detail
45{
46
47#ifdef __METAL__
48typedef ulong u64;
49typedef long i64;
50typedef uint u32;
51#else
52typedef uint64_t u64;
53typedef int64_t i64;
54typedef uint32_t u32;
55#endif
56
57#define GPUCA_B64_ALWAYS inline __attribute__((always_inline))
58#ifdef __METAL__
59// The arithmetic has to stay out of line: inlined into a kernel it drops the
60// occupancy (maxTotalThreadsPerThreadgroup 832 -> 384) and runs two to five
61// times slower than the call.
62#define GPUCA_B64_OP __attribute__((noinline))
63#else
64#define GPUCA_B64_OP inline
65#endif
66
67#ifdef __METAL__
68// metal::clz is not constant-evaluable, which would keep the whole class out of
69// constant expressions; the builtin folds to the same ctlz at run time.
70GPUCA_B64_ALWAYS constexpr int32_t clz64(u64 x) { return x ? __builtin_clzl(x) : 64; }
71GPUCA_B64_ALWAYS constexpr int32_t clz32(u32 x) { return x ? __builtin_clz(x) : 32; }
72GPUCA_B64_ALWAYS constexpr u32 asu32(float f) { return as_type<u32>(f); }
73GPUCA_B64_ALWAYS constexpr float asf32(u32 u) { return as_type<float>(u); }
74#else
75GPUCA_B64_ALWAYS constexpr int32_t clz64(u64 x) { return x ? __builtin_clzll(x) : 64; }
76GPUCA_B64_ALWAYS constexpr int32_t clz32(u32 x) { return x ? __builtin_clz(x) : 32; }
77
78GPUCA_B64_ALWAYS constexpr u32 asu32(float f) { return __builtin_bit_cast(uint32_t, f); }
79GPUCA_B64_ALWAYS constexpr float asf32(u32 u) { return __builtin_bit_cast(float, u); }
80#endif
81
82// The high half of a 64x64 product, from 32-bit partial products. Neither
83// metal::mulhi nor __int128 can appear in a constant expression, and the
84// program-scope constants are constexpr.
86{
87 const u64 al = a & 0xffffffffu, ah = a >> 32, bl = b & 0xffffffffu, bh = b >> 32;
88 const u64 ll = al * bl, lh = al * bh, hl = ah * bl, hh = ah * bh;
89 const u64 mid = (ll >> 32) + (lh & 0xffffffffu) + (hl & 0xffffffffu);
90 return hh + (lh >> 32) + (hl >> 32) + (mid >> 32);
91}
93{
94 u64 h = mulhiu((u64)a, (u64)b);
95 h -= (a < 0) ? (u64)b : 0ULL;
96 h -= (b < 0) ? (u64)a : 0ULL;
97 return (i64)h;
98}
99
100#define GPUCA_B64_SIGN 0x8000000000000000ULL
101#define GPUCA_B64_FRAC 0x000fffffffffffffULL
102#define GPUCA_B64_IMPL 0x0010000000000000ULL
103#define GPUCA_B64_QUIET 0x0008000000000000ULL
104#define GPUCA_B64_INF 0x7ff0000000000000ULL
105#define GPUCA_B64_DNAN 0x7ff8000000000000ULL // ARM default NaN
106
107// right shift keeping a sticky bit; any count >= 0, no shift count ever reaches 64
109{
110 const int32_t sc = s > 63 ? 63 : s;
111 const u64 r = m >> sc;
112 const u64 lost = (m << (63 - sc)) << 1;
113 const bool big = s > 63;
114 const u64 rr = big ? 0ULL : r;
115 const u64 st = big ? m : lost;
116 return rr | (st != 0 ? 1ULL : 0ULL);
117}
118
119// m: leading bit at 62 for a normal result, 10 guard bits below the 53-bit significand,
120// value = m * 2^(e - 1085) with e the biased exponent. Packing (e-1)<<52 + m lets a
121// rounding carry ripple into the exponent.
123{
124 if (e >= 0x7ff) {
125 return sign | GPUCA_B64_INF;
126 }
127 if (e <= 0) {
128 m = shrJam(m, 1 - e);
129 e = 1;
130 }
131 const u64 r = m & 0x3ffULL;
132 m >>= 10;
133 if (r > 0x200ULL || (r == 0x200ULL && (m & 1ULL))) {
134 ++m;
135 }
136 return sign | (((u64)(e - 1) << 52) + m);
137}
138
139// an sNaN operand wins over a qNaN one, among equals the first wins, the result is quietened
140GPUCA_B64_ALWAYS constexpr u64 propNaN(u64 a, u64 b, bool an, bool bn)
141{
142 const bool as = an && !(a & GPUCA_B64_QUIET), bs = bn && !(b & GPUCA_B64_QUIET);
143 if (as) {
144 return a | GPUCA_B64_QUIET;
145 }
146 if (bs) {
147 return b | GPUCA_B64_QUIET;
148 }
149 return an ? a : b;
150}
151
152GPUCA_B64_OP constexpr u64 addsub(u64 a, u64 b0, bool neg)
153{
154 const u64 b = neg ? (b0 ^ GPUCA_B64_SIGN) : b0;
155 const bool sw = (a & ~GPUCA_B64_SIGN) < (b & ~GPUCA_B64_SIGN);
156 const u64 x = sw ? b : a, y = sw ? a : b; // |x| >= |y|
157 int32_t ex = (int32_t)((x >> 52) & 0x7ff), ey = (int32_t)((y >> 52) & 0x7ff);
158 u64 mx = x & GPUCA_B64_FRAC, my = y & GPUCA_B64_FRAC;
159 if (ex == 0x7ff) { // y can only be inf/NaN if x is too
160 const bool an = ((a >> 52) & 0x7ff) == 0x7ff && (a & GPUCA_B64_FRAC) != 0, bn = ((b0 >> 52) & 0x7ff) == 0x7ff && (b0 & GPUCA_B64_FRAC) != 0;
161 if (an || bn) {
162 return propNaN(a, b0, an, bn);
163 }
164 if (ey == 0x7ff) {
165 return ((x ^ y) & GPUCA_B64_SIGN) ? GPUCA_B64_DNAN : x;
166 }
167 return x;
168 }
169 const u64 sx = x & GPUCA_B64_SIGN;
170 const bool sub = ((x ^ y) >> 63) != 0;
171 mx = (ex ? (mx | GPUCA_B64_IMPL) : mx) << 10;
172 ex = ex ? ex : 1;
173 my = (ey ? (my | GPUCA_B64_IMPL) : my) << 10;
174 ey = ey ? ey : 1;
175 my = shrJam(my, ex - ey);
176 const u64 s = sub ? mx - my : mx + my;
177 const bool carry = (s >> 63) != 0;
178 const int32_t lz = clz64(s) - 1; // -1 on carry, 63 on zero
179 const u64 sN = carry ? ((s >> 1) | (s & 1ULL)) : (s << (lz & 63));
180 const int32_t eN = carry ? ex + 1 : ex - lz;
181 const bool zero = s == 0;
182 return roundPack((zero && sub) ? 0ULL : sx, zero ? -1 : eN, sN);
183}
184
186{
187 const u64 sign = (a ^ b) & GPUCA_B64_SIGN;
188 int32_t ea = (int32_t)((a >> 52) & 0x7ff), eb = (int32_t)((b >> 52) & 0x7ff);
189 u64 ma = a & GPUCA_B64_FRAC, mb = b & GPUCA_B64_FRAC;
190 if (ea == 0x7ff || eb == 0x7ff) {
191 const bool an = ea == 0x7ff && ma != 0, bn = eb == 0x7ff && mb != 0;
192 if (an || bn) {
193 return propNaN(a, b, an, bn);
194 }
195 if ((ea == 0 && ma == 0) || (eb == 0 && mb == 0)) {
196 return GPUCA_B64_DNAN; // inf * 0
197 }
198 return sign | GPUCA_B64_INF;
199 }
200 if (ea == 0) {
201 if (ma == 0) {
202 return sign;
203 }
204 const int32_t lz = clz64(ma) - 11;
205 ma <<= lz;
206 ea = 1 - lz;
207 } else {
208 ma |= GPUCA_B64_IMPL;
209 }
210 if (eb == 0) {
211 if (mb == 0) {
212 return sign;
213 }
214 const int32_t lz = clz64(mb) - 11;
215 mb <<= lz;
216 eb = 1 - lz;
217 } else {
218 mb |= GPUCA_B64_IMPL;
219 }
220 const u64 lo = ma * mb, hi = mulhiu(ma, mb); // 106-bit product in [2^104, 2^106)
221 const bool top = (hi & (1ULL << 41)) != 0;
222 const u64 m1 = (hi << 21) | (lo >> 43), m0 = (hi << 22) | (lo >> 42);
223 const u64 st = top ? (lo & ((1ULL << 43) - 1)) : (lo & ((1ULL << 42) - 1));
224 const u64 m = (top ? m1 : m0) | (st != 0 ? 1ULL : 0ULL);
225 return roundPack(sign, ea + eb - 1023 + (top ? 1 : 0), m);
226}
227
229{
230 const u64 sign = (a ^ b) & GPUCA_B64_SIGN;
231 int32_t ea = (int32_t)((a >> 52) & 0x7ff), eb = (int32_t)((b >> 52) & 0x7ff);
232 u64 ma = a & GPUCA_B64_FRAC, mb = b & GPUCA_B64_FRAC;
233 if (ea == 0x7ff || eb == 0x7ff) {
234 const bool an = ea == 0x7ff && ma != 0, bn = eb == 0x7ff && mb != 0;
235 if (an || bn) {
236 return propNaN(a, b, an, bn);
237 }
238 if (ea == 0x7ff && eb == 0x7ff) {
239 return GPUCA_B64_DNAN; // inf / inf
240 }
241 return ea == 0x7ff ? (sign | GPUCA_B64_INF) : sign; // inf / x, x / inf
242 }
243 if (eb == 0 && mb == 0) {
244 return (ea == 0 && ma == 0) ? GPUCA_B64_DNAN : (sign | GPUCA_B64_INF); // x / 0
245 }
246 if (ea == 0) {
247 if (ma == 0) {
248 return sign;
249 }
250 const int32_t lz = clz64(ma) - 11;
251 ma <<= lz;
252 ea = 1 - lz;
253 } else {
254 ma |= GPUCA_B64_IMPL;
255 }
256 if (eb == 0) {
257 const int32_t lz = clz64(mb) - 11;
258 mb <<= lz;
259 eb = 1 - lz;
260 } else {
261 mb |= GPUCA_B64_IMPL;
262 }
263 const bool lt = ma < mb;
264 const int32_t e = ea - eb + 1023 - (lt ? 1 : 0);
265 const u64 A2 = lt ? (ma << 1) : ma; // A2 / mb in [1, 2)
266 // reciprocal R ~ 2^114 / mb in (2^61, 2^62], seeded from a float division on the top 24 bits
267 const u32 rb = asu32(1.0f / (float)(u32)(mb >> 29));
268 u64 R = (u64)((rb & 0x7fffffu) | 0x800000u) << ((int32_t)((rb >> 23) & 0xff) - 127 + 62);
269 const u64 Bn = mb << 11;
270 for (int32_t it = 0; it < 2; ++it) {
271 const i64 E = (i64)(1ULL << 61) - (i64)mulhiu(Bn, R);
272 R = (u64)((i64)R + mulhis((i64)R, E * 8)); // not E << 3: shifting a negative value is not a constant expression
273 }
274 R = R > (1ULL << 62) ? (1ULL << 62) : R;
275 // Q ~ A2 * 2^62 / mb, then the exact remainder, which fits in a signed 64-bit
276 // word because Q is within a few units
277 u64 Q = mulhiu(A2 << 10, (R << 2) - 1);
278 i64 rem = (i64)((A2 << 62) - Q * mb);
279 const i64 adj = mulhis(rem, (i64)R) >> 50;
280 Q = (u64)((i64)Q + adj);
281 rem -= adj * (i64)mb;
282 // after adj the remainder is within one divisor of [0, mb): one predicated step each way
283 const bool ng = rem < 0;
284 Q = ng ? Q - 1 : Q;
285 rem = ng ? rem + (i64)mb : rem;
286 const bool bg = rem >= (i64)mb;
287 Q = bg ? Q + 1 : Q;
288 rem = bg ? rem - (i64)mb : rem;
289 return roundPack(sign, e, Q | (rem != 0 ? 1ULL : 0ULL));
290}
291
292// an int32 or a uint32 always fits the 53-bit significand, so these are exact
294{
295 if (x == 0) {
296 return 0ULL;
297 }
298 const int32_t lz = clz32(x);
299 return ((u64)(31 - lz + 1023) << 52) | (((u64)x << (21 + lz)) & GPUCA_B64_FRAC);
300}
301
303{
304 return fromU32(x < 0 ? (u32)(-(i64)x) : (u32)x) | (x < 0 ? GPUCA_B64_SIGN : 0ULL);
305}
306
308{
309 const u32 u = asu32(f);
310 const u64 sign = (u64)(u & 0x80000000u) << 32;
311 int32_t e = (int32_t)((u >> 23) & 0xff);
312 u32 m = u & 0x7fffffu;
313 if (e == 0xff) {
314 return sign | GPUCA_B64_INF | ((u64)m << 29) | (m ? GPUCA_B64_QUIET : 0ULL);
315 }
316 if (e == 0) {
317 if (m == 0) {
318 return sign;
319 }
320 const int32_t lz = clz32(m) - 8;
321 m <<= lz;
322 e = 1 - lz;
323 }
324 return sign | ((u64)(e - 127 + 1023) << 52) | ((u64)(m & 0x7fffffu) << 29);
325}
326
327// binary64 -> binary32, round to nearest even, subnormals, inf, NaN (payload kept, quietened)
328GPUCA_B64_OP constexpr float toFloat(u64 d)
329{
330 const u32 sign = (u32)(d >> 32) & 0x80000000u;
331 const int32_t be = (int32_t)((d >> 52) & 0x7ff);
332 const u32 man = (u32)((d & GPUCA_B64_FRAC) >> 29);
333 const u32 drop = (u32)d & 0x1fffffffu;
334 u32 bits = 0;
335 if (be == 0x7ff) {
336 bits = sign | 0x7f800000u | man | ((d & GPUCA_B64_FRAC) ? 0x400000u : 0u);
337 } else if (be == 0) {
338 bits = sign;
339 } else {
340 const int32_t e = be - 1023 + 127;
341 if (e >= 0xff) {
342 bits = sign | 0x7f800000u;
343 } else if (e > 0) {
344 bits = sign | ((u32)e << 23) | man;
345 if ((drop & 0x10000000u) && ((drop & 0x0fffffffu) || (man & 1u))) {
346 bits += 1u;
347 }
348 } else if (e > -24) {
349 const u32 full = man | 0x800000u;
350 const u32 sh = (u32)(1 - e);
351 const u32 lost = full & ((1u << sh) - 1u);
352 const u32 halfb = 1u << (sh - 1);
353 u32 sub = full >> sh;
354 if (lost > halfb || (lost == halfb && ((sub & 1u) || drop))) {
355 sub += 1u;
356 }
357 bits = sign | sub;
358 } else {
359 bits = sign;
360 }
361 }
362 return asf32(bits);
363}
364
365} // namespace binary64_detail
366
368{
369 public:
371 GPUdi() constexpr GPUdoubleBinary64(float v) : mBits(binary64_detail::fromFloat(v)) {}
372 GPUdi() constexpr operator float() const { return binary64_detail::toFloat(mBits); }
373
374 GPUdi() static constexpr GPUdoubleBinary64 fromBits(binary64_detail::u64 b) { return GPUdoubleBinary64(b, FromBits{}); }
375 GPUdi() constexpr binary64_detail::u64 bits() const { return mBits; }
376
377 GPUdi() constexpr GPUdoubleBinary64 operator-() const { return fromBits(mBits ^ GPUCA_B64_SIGN); }
378 GPUdi() constexpr GPUdoubleBinary64 operator+(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::addsub(mBits, b.mBits, false)); }
379 GPUdi() constexpr GPUdoubleBinary64 operator-(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::addsub(mBits, b.mBits, true)); }
380 GPUdi() constexpr GPUdoubleBinary64 operator*(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::mul(mBits, b.mBits)); }
381 GPUdi() constexpr GPUdoubleBinary64 operator/(GPUdoubleBinary64 b) const { return fromBits(binary64_detail::div(mBits, b.mBits)); }
382
383 GPUdi() constexpr GPUdoubleBinary64 operator+(float b) const { return *this + GPUdoubleBinary64(b); }
384 GPUdi() constexpr GPUdoubleBinary64 operator-(float b) const { return *this - GPUdoubleBinary64(b); }
385 GPUdi() constexpr GPUdoubleBinary64 operator*(float b) const { return *this * GPUdoubleBinary64(b); }
386 GPUdi() constexpr GPUdoubleBinary64 operator/(float b) const { return *this / GPUdoubleBinary64(b); }
387#ifndef __METAL__
388 GPUdi() constexpr GPUdoubleBinary64 operator+(double b) const { return *this + (float)b; }
389 GPUdi() constexpr GPUdoubleBinary64 operator-(double b) const { return *this - (float)b; }
390 GPUdi() constexpr GPUdoubleBinary64 operator*(double b) const { return *this * (float)b; }
391 GPUdi() constexpr GPUdoubleBinary64 operator/(double b) const { return *this / (float)b; }
392#endif
393
394 // integral operands: an exact match, so `2 * x` does not sit ambiguously between
395 // converting the int up and converting *this down
396 GPUdi() constexpr GPUdoubleBinary64 operator+(int32_t b) const { return *this + fromBits(binary64_detail::fromI32(b)); }
397 GPUdi() constexpr GPUdoubleBinary64 operator-(int32_t b) const { return *this - fromBits(binary64_detail::fromI32(b)); }
398 GPUdi() constexpr GPUdoubleBinary64 operator*(int32_t b) const { return *this * fromBits(binary64_detail::fromI32(b)); }
399 GPUdi() constexpr GPUdoubleBinary64 operator/(int32_t b) const { return *this / fromBits(binary64_detail::fromI32(b)); }
400 GPUdi() constexpr GPUdoubleBinary64 operator+(uint32_t b) const { return *this + fromBits(binary64_detail::fromU32(b)); }
401 GPUdi() constexpr GPUdoubleBinary64 operator-(uint32_t b) const { return *this - fromBits(binary64_detail::fromU32(b)); }
402 GPUdi() constexpr GPUdoubleBinary64 operator*(uint32_t b) const { return *this * fromBits(binary64_detail::fromU32(b)); }
403 GPUdi() constexpr GPUdoubleBinary64 operator/(uint32_t b) const { return *this / fromBits(binary64_detail::fromU32(b)); }
404#ifdef __METAL__
405 // the same surface for an object that lives in the constant address space, which
406 // a generic `this` does not reach
407 GPUdi() constexpr GPUdoubleBinary64(float v) constant : mBits(binary64_detail::fromFloat(v)) {}
408 GPUdi() constexpr operator float() constant { return binary64_detail::toFloat(mBits); }
409 GPUdi() constexpr GPUdoubleBinary64 operator+(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::addsub(mBits, b.mBits, false)); }
410 GPUdi() constexpr GPUdoubleBinary64 operator-(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::addsub(mBits, b.mBits, true)); }
411 GPUdi() constexpr GPUdoubleBinary64 operator*(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::mul(mBits, b.mBits)); }
412 GPUdi() constexpr GPUdoubleBinary64 operator/(GPUdoubleBinary64 b) constant { return fromBits(binary64_detail::div(mBits, b.mBits)); }
413 GPUdi() constexpr GPUdoubleBinary64 operator+(float b) constant { return *this + GPUdoubleBinary64(b); }
414 GPUdi() constexpr GPUdoubleBinary64 operator-(float b) constant { return *this - GPUdoubleBinary64(b); }
415 GPUdi() constexpr GPUdoubleBinary64 operator*(float b) constant { return *this * GPUdoubleBinary64(b); }
416 GPUdi() constexpr GPUdoubleBinary64 operator/(float b) constant { return *this / GPUdoubleBinary64(b); }
417 GPUdi() constexpr GPUdoubleBinary64 operator*(int32_t b) constant { return *this * fromBits(binary64_detail::fromI32(b)); }
418 GPUdi() constexpr GPUdoubleBinary64 operator/(int32_t b) constant { return *this / fromBits(binary64_detail::fromI32(b)); }
419#endif
420
421 GPUdi() GPUdoubleBinary64& operator+=(GPUdoubleBinary64 b) { return *this = *this + b; }
422 GPUdi() GPUdoubleBinary64& operator-=(GPUdoubleBinary64 b) { return *this = *this - b; }
423 GPUdi() GPUdoubleBinary64& operator*=(GPUdoubleBinary64 b) { return *this = *this * b; }
424 GPUdi() GPUdoubleBinary64& operator/=(GPUdoubleBinary64 b) { return *this = *this / b; }
425
426 private:
427 struct FromBits {
428 };
429 GPUdi() constexpr GPUdoubleBinary64(binary64_detail::u64 b, FromBits) : mBits(b) {}
430
432};
433
434static_assert(sizeof(GPUdoubleBinary64) == 8, "the emulated double must match the size of a real one");
435static_assert(alignof(GPUdoubleBinary64) == 8, "the emulated double must match the alignment of a real one");
436
437GPUdi() constexpr GPUdoubleBinary64 operator+(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) + b; }
438GPUdi() constexpr GPUdoubleBinary64 operator-(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) - b; }
439GPUdi() constexpr GPUdoubleBinary64 operator*(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) * b; }
440GPUdi() constexpr GPUdoubleBinary64 operator/(int32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromI32(a)) / b; }
441GPUdi() constexpr GPUdoubleBinary64 operator+(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) + b; }
442GPUdi() constexpr GPUdoubleBinary64 operator-(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) - b; }
443GPUdi() constexpr GPUdoubleBinary64 operator*(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) * b; }
444GPUdi() constexpr GPUdoubleBinary64 operator/(uint32_t a, GPUdoubleBinary64 b) { return GPUdoubleBinary64::fromBits(binary64_detail::fromU32(a)) / b; }
445GPUdi() constexpr GPUdoubleBinary64 operator+(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) + b; }
446GPUdi() constexpr GPUdoubleBinary64 operator-(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) - b; }
447GPUdi() constexpr GPUdoubleBinary64 operator*(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) * b; }
448GPUdi() constexpr GPUdoubleBinary64 operator/(float a, GPUdoubleBinary64 b) { return GPUdoubleBinary64(a) / b; }
449#ifndef __METAL__
450GPUdi() constexpr GPUdoubleBinary64 operator+(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) + b; }
451GPUdi() constexpr GPUdoubleBinary64 operator-(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) - b; }
452GPUdi() constexpr GPUdoubleBinary64 operator*(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) * b; }
453GPUdi() constexpr GPUdoubleBinary64 operator/(double a, GPUdoubleBinary64 b) { return GPUdoubleBinary64((float)a) / b; }
454#endif
455
456// rounds once, as `someFloat += someDouble` does on the host
457#ifdef __METAL__
458GPUdi() thread float& operator+=(thread float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
459GPUdi() device float& operator+=(device float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
460GPUdi() threadgroup float& operator+=(threadgroup float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
461#else
462GPUdi() float& operator+=(float& a, GPUdoubleBinary64 b) { return a = (float)(GPUdoubleBinary64(a) + b); }
463#endif
464
465namespace binary64_detail
466{
467// sin and cos are fdlibm's __kernel_sin / __kernel_cos and the medium-range
468// branch of __ieee754_rem_pio2. The coefficients are spelled as bit patterns
469// because MSL has no double literals. The argument reduction is exact for
470// |x| <= 2^19 * pi/2; beyond that the accuracy degrades gracefully.
471GPUCA_B64_OP GPUdoubleBinary64 kernelSin(GPUdoubleBinary64 x, GPUdoubleBinary64 y, bool iy)
472{
473 typedef GPUdoubleBinary64 b64;
474 const b64 S1 = b64::fromBits(0xBFC5555555555549ULL);
475 const b64 S2 = b64::fromBits(0x3F8111111110F8A6ULL);
476 const b64 S3 = b64::fromBits(0xBF2A01A019C161D5ULL);
477 const b64 S4 = b64::fromBits(0x3EC71DE357B1FE7DULL);
478 const b64 S5 = b64::fromBits(0xBE5AE5E68A2B9CEBULL);
479 const b64 S6 = b64::fromBits(0x3DE5D93A5ACFD57CULL);
480 const b64 z = x * x;
481 const b64 v = z * x;
482 const b64 r = S2 + z * (S3 + z * (S4 + z * (S5 + z * S6)));
483 if (!iy) {
484 return x + v * (S1 + z * r);
485 }
486 return x - ((z * (b64::fromBits(0x3FE0000000000000ULL) * y - v * r) - y) - v * S1);
487}
488
489GPUCA_B64_OP GPUdoubleBinary64 kernelCos(GPUdoubleBinary64 x, GPUdoubleBinary64 y)
490{
491 typedef GPUdoubleBinary64 b64;
492 const b64 C1 = b64::fromBits(0x3FA555555555554CULL);
493 const b64 C2 = b64::fromBits(0xBF56C16C16C15177ULL);
494 const b64 C3 = b64::fromBits(0x3EFA01A019CB1590ULL);
495 const b64 C4 = b64::fromBits(0xBE927E4F809C52ADULL);
496 const b64 C5 = b64::fromBits(0x3E21EE9EBDB4B1C4ULL);
497 const b64 C6 = b64::fromBits(0xBDA8FAE9BE8838D4ULL);
498 const b64 one = b64::fromBits(0x3FF0000000000000ULL);
499 const b64 oneHalf = b64::fromBits(0x3FE0000000000000ULL);
500 const u32 ix = (u32)(x.bits() >> 32) & 0x7fffffffu;
501 const b64 z = x * x;
502 const b64 r = z * (C1 + z * (C2 + z * (C3 + z * (C4 + z * (C5 + z * C6)))));
503 if (ix < 0x3FD33333u) { // |x| < 0.3
504 return one - (oneHalf * z - (z * r - x * y));
505 }
506 const b64 qx = (ix > 0x3FE90000u) ? b64::fromBits(0x3FD2000000000000ULL) : b64::fromBits((u64)(ix - 0x00200000u) << 32); // 0.28125, else |x| / 4
507 return (one - qx) - ((oneHalf * z - qx) - (z * r - x * y));
508}
509
510GPUCA_B64_ALWAYS int32_t truncToInt32(GPUdoubleBinary64 x)
511{
512 const u64 b = x.bits();
513 const int32_t e = (int32_t)((b >> 52) & 0x7ff) - 1023;
514 if (e < 0) {
515 return 0;
516 }
517 const u64 m = (b & GPUCA_B64_FRAC) | GPUCA_B64_IMPL;
518 const int32_t v = (int32_t)(e >= 52 ? (m << (e - 52)) : (m >> (52 - e)));
519 return (b & GPUCA_B64_SIGN) ? -v : v;
520}
521
522struct SinCosPair {
523 GPUdoubleBinary64 s, c;
524};
525
526GPUCA_B64_OP SinCosPair sincos(GPUdoubleBinary64 x)
527{
528 typedef GPUdoubleBinary64 b64;
529 const u32 ix = (u32)(x.bits() >> 32) & 0x7fffffffu;
530 SinCosPair out;
531 if (ix >= 0x7FF00000u) { // inf or NaN
532 out.s = out.c = b64::fromBits(GPUCA_B64_DNAN);
533 return out;
534 }
535
536 if (ix < 0x3E400000u) { // |x| < 2^-27, where the sign of a zero x has to survive
537 out.s = x;
538 out.c = b64::fromBits(0x3FF0000000000000ULL);
539 return out;
540 }
541
542 b64 y0 = x, y1 = b64::fromBits(0ULL);
543 int32_t n = 0;
544 const bool reduced = ix > 0x3FE921FBu; // |x| > pi/4
545 if (reduced) {
546 const b64 t = b64::fromBits(x.bits() & ~GPUCA_B64_SIGN);
547 n = truncToInt32(t * b64::fromBits(0x3FE45F306DC9C883ULL) + b64::fromBits(0x3FE0000000000000ULL));
548 const b64 fn = b64((float)n);
549 // Cody-Waite with pi/2 split over three terms, good to 151 bits
550 b64 r = t - fn * b64::fromBits(0x3FF921FB54400000ULL);
551 b64 s = r;
552 b64 w = fn * b64::fromBits(0x3DD0B4611A600000ULL);
553 r = s - w;
554 w = fn * b64::fromBits(0x3BA3198A2E037073ULL) - ((s - r) - w);
555 s = r;
556 w = fn * b64::fromBits(0x3BA3198A2E000000ULL);
557 r = s - w;
558 w = fn * b64::fromBits(0x397B839A252049C1ULL) - ((s - r) - w);
559 y0 = r - w;
560 y1 = (r - y0) - w;
561 if (x.bits() & GPUCA_B64_SIGN) {
562 y0 = -y0;
563 y1 = -y1;
564 n = -n;
565 }
566 }
567
568 switch (n & 3) {
569 case 0:
570 out.s = kernelSin(y0, y1, reduced);
571 out.c = kernelCos(y0, y1);
572 break;
573 case 1:
574 out.s = kernelCos(y0, y1);
575 out.c = -kernelSin(y0, y1, reduced);
576 break;
577 case 2:
578 out.s = -kernelSin(y0, y1, reduced);
579 out.c = -kernelCos(y0, y1);
580 break;
581 default:
582 out.s = -kernelCos(y0, y1);
583 out.c = kernelSin(y0, y1, reduced);
584 break;
585 }
586 return out;
587}
588} // namespace binary64_detail
589
590} // namespace o2::gpu
591
592#endif // GPUCOMMONDOUBLEBINARY64_H
unsigned int uint32_t
#define GPUCA_B64_IMPL
#define GPUCA_B64_INF
#define GPUCA_B64_ALWAYS
#define GPUCA_B64_FRAC
#define GPUCA_B64_DNAN
#define GPUCA_B64_OP
#define GPUCA_B64_SIGN
#define GPUCA_B64_QUIET
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 e
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
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of< 0:reject at max -n hits") AddOptionRTC(rejectIFCLowRadiusCluster, uint8_t, 1, "", 0, "Reject clusters that get the IFC mask error during refit") AddOptionRTC(dEdxTruncLow, uint8_t, 2, "", 0, "Low truncation threshold, fraction of 128") AddOptionRTC(dEdxTruncHigh, uint8_t, 77, "", 0, "High truncation threshold, fraction of 128") AddOptionRTC(extrapolationTracking, int8_t, 1, "", 0, "Enable Extrapolation Tracking(prolong tracks to adjacent sectors to find short segments)") AddOptionRTC(disableRefitAttachment, uint8_t, 0, "", 0, "Bitmask to disable certain attachment steps during refit(1:attachment, 2:propagation, 4:loop following, 8:mirroring)") AddOptionRTC(rejectionStrategy, uint8_t, o2::gpu::GPUSettings::RejectionStrategyA, "", 0, "Enable rejection of TPC clusters for compression(0=no, 1=strategy A, 2=strategy B)") AddOptionRTC(mergeLoopersAfterburner, uint8_t, 1, "", 0, "Run afterburner for additional looper merging") AddOptionRTC(compressionTypeMask, uint8_t, o2::gpu::GPUSettings::CompressionFull, "", 0, "TPC Compression mode bits(1=truncate charge/width LSB, 2=differences, 4=track-model)") AddOptionRTC(compressionSortOrder, uint8_t, o2::gpu::GPUSettings::SortTime, "", 0, "Sort order of TPC compression(0=time, 1=pad, 2=Z-time-pad, 3=Z-pad-time, 4=no sorting(use incoming order))") AddOptionRTC(sigBitsCharge, uint8_t, 4, "", 0, "Number of significant bits for TPC cluster charge in compression mode 1") AddOptionRTC(sigBitsWidth, uint8_t, 3, "", 0, "Number of significant bits for TPC cluster width in compression mode 1") AddOptionRTC(dropLoopers, uint8_t, 0, "", 0, "Drop looping tracks starting from second loop") AddOptionRTC(mergerInterpolateErrors, uint8_t, 1, "", 0, "Use interpolation instead of extrapolation for chi2 based cluster rejection") AddOptionRTC(mergerInterpolateRejectAlsoOnCurrentPosition, uint8_t, 1, "", 0, "When using mergerInterpolateErrors, reject based on chi2 twice computed with interpolated and current track position starting from NDF > mergerNonInterpolateRejectMinNDF Merge tracks accross the central electrode Enable PID response Add sub threshold clusters in TPC dEdx computation OR mask of TPC flags that will reject the cluster in alternative dEdx Reject edge clusters based on uncorrected track Y during track fit Increase decrease removal by when on this number of pads from the edge h configStandalone Reconstruction rec_trd Max chi2 for TRD tracklets to be matched to a track Chi2 cut for strict matching mode Number of sigmas for ITS TPC track time error estimate Addition to search road around track prolongation along Y in cm Optimal tracklet rphi resolution in maximum chi2 per attached tracklet for TRD tracks take into account the full likelihood of the slope including exponential tails Tracks with less attached tracklets are discarded after the tracking Post trigger Pile up integration time in BCs fits all iterations but the last one in projections Use material lookup table for TPC refit Apply workarounds to avoid cyclic merge should not be needed tpc dyn configStandalone Processing proc_rtc Replace constant variables static by constexpr expressions Compile RTC in deterministic with NO_FAST_MATH flags and GPUCA_DETERMINISTIC_MODE define Use RTC to optimize GPU code h configStandalone Processing proc_rtctech Use a file lock to serialize access to the cache folder int32_t
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of< 0:reject at max -n hits") AddOptionRTC(rejectIFCLowRadiusCluster, uint8_t, 1, "", 0, "Reject clusters that get the IFC mask error during refit") AddOptionRTC(dEdxTruncLow, uint8_t, 2, "", 0, "Low truncation threshold, fraction of 128") AddOptionRTC(dEdxTruncHigh, uint8_t, 77, "", 0, "High truncation threshold, fraction of 128") AddOptionRTC(extrapolationTracking, int8_t, 1, "", 0, "Enable Extrapolation Tracking(prolong tracks to adjacent sectors to find short segments)") AddOptionRTC(disableRefitAttachment, uint8_t, 0, "", 0, "Bitmask to disable certain attachment steps during refit(1:attachment, 2:propagation, 4:loop following, 8:mirroring)") AddOptionRTC(rejectionStrategy, uint8_t, o2::gpu::GPUSettings::RejectionStrategyA, "", 0, "Enable rejection of TPC clusters for compression(0=no, 1=strategy A, 2=strategy B)") AddOptionRTC(mergeLoopersAfterburner, uint8_t, 1, "", 0, "Run afterburner for additional looper merging") AddOptionRTC(compressionTypeMask, uint8_t, o2::gpu::GPUSettings::CompressionFull, "", 0, "TPC Compression mode bits(1=truncate charge/width LSB, 2=differences, 4=track-model)") AddOptionRTC(compressionSortOrder, uint8_t, o2::gpu::GPUSettings::SortTime, "", 0, "Sort order of TPC compression(0=time, 1=pad, 2=Z-time-pad, 3=Z-pad-time, 4=no sorting(use incoming order))") AddOptionRTC(sigBitsCharge, uint8_t, 4, "", 0, "Number of significant bits for TPC cluster charge in compression mode 1") AddOptionRTC(sigBitsWidth, uint8_t, 3, "", 0, "Number of significant bits for TPC cluster width in compression mode 1") AddOptionRTC(dropLoopers, uint8_t, 0, "", 0, "Drop looping tracks starting from second loop") AddOptionRTC(mergerInterpolateErrors, uint8_t, 1, "", 0, "Use interpolation instead of extrapolation for chi2 based cluster rejection") AddOptionRTC(mergerInterpolateRejectAlsoOnCurrentPosition, uint8_t, 1, "", 0, "When using mergerInterpolateErrors, reject based on chi2 twice computed with interpolated and current track position starting from NDF > mergerNonInterpolateRejectMinNDF Merge tracks accross the central electrode Enable PID response Add sub threshold clusters in TPC dEdx computation OR mask of TPC flags that will reject the cluster in alternative dEdx Reject edge clusters based on uncorrected track Y during track fit Increase decrease removal by when on this number of pads from the edge h configStandalone Reconstruction rec_trd Max chi2 for TRD tracklets to be matched to a track Chi2 cut for strict matching mode Number of sigmas for ITS TPC track time error estimate Addition to search road around track prolongation along Y in cm Optimal tracklet rphi resolution in maximum chi2 per attached tracklet for TRD tracks take into account the full likelihood of the slope including exponential tails Tracks with less attached tracklets are discarded after the tracking Post trigger Pile up integration time in BCs fits all iterations but the last one in projections Use material lookup table for TPC refit Apply workarounds to avoid cyclic merge should not be needed tpc dyn configStandalone Processing proc_rtc Replace constant variables static by constexpr expressions Compile RTC in deterministic with NO_FAST_MATH flags and GPUCA_DETERMINISTIC_MODE define Use RTC to optimize GPU code h configStandalone Processing proc_rtctech Use a file lock to serialize access to the cache folder Print launch bounds used for RTC code as debugging for exit after Allow RTC with slave GPUReconstruction instances with optConstexpr and optSpecialcode Maximum number of RTC cache files per backend in the cache the oldest are Prepend RTC compilation commands by this string Load a parameter object containing the launch bounds from a file h configStandalone Processing proc_param TPC Cluster Z Error Parameterization h configStandalone Processing settings for neural network proc_nn std::string Specify inference device(cpu(default), rocm, cuda)") AddOption(nnInferenceDeviceId
configStandalone Reconstruction rec_tpc float
uint32_t one
Definition RawData.h:4
uint32_t c
Definition RawData.h:2
benchmark::State & st
Class for time synchronization of RawReader instances.
GPUdi() static const expr GPUdoubleBinary64 fromBits(binary64_detail
GPUdi() const expr operator float() const
GPUdi() const expr binary64_detail
GPUdDefault() GPUdoubleBinary64()=default
GPUdi() const expr GPUdoubleBinary64(binary64_detail
GPUdi() const expr GPUdoubleBinary64(float v)
GPUdi() GPUdoubleBinary64 &operator-
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
const GLfloat * m
Definition glcorearb.h:4066
GLuint GLfloat GLfloat GLfloat GLfloat y1
Definition glcorearb.h:5034
const GLdouble * v
Definition glcorearb.h:832
GLdouble GLdouble GLdouble GLdouble top
Definition glcorearb.h:4077
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLenum GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const void * bits
Definition glcorearb.h:4150
GLboolean r
Definition glcorearb.h:1233
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLubyte GLubyte GLubyte GLubyte w
Definition glcorearb.h:852
GLuint GLfloat GLfloat y0
Definition glcorearb.h:5034
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
GPUCA_B64_OP constexpr u64 addsub(u64 a, u64 b0, bool neg)
GPUCA_B64_OP constexpr float toFloat(u64 d)
GPUCA_B64_ALWAYS constexpr u64 propNaN(u64 a, u64 b, bool an, bool bn)
GPUCA_B64_ALWAYS constexpr u64 mulhiu(u64 a, u64 b)
GPUCA_B64_ALWAYS constexpr u64 fromI32(int32_t x)
GPUCA_B64_ALWAYS constexpr int32_t clz64(u64 x)
GPUCA_B64_ALWAYS constexpr i64 mulhis(i64 a, i64 b)
GPUCA_B64_ALWAYS constexpr u64 shrJam(u64 m, int32_t s)
GPUCA_B64_OP constexpr u64 div(u64 a, u64 b)
GPUCA_B64_ALWAYS constexpr u64 roundPack(u64 sign, int32_t e, u64 m)
GPUCA_B64_ALWAYS constexpr float asf32(u32 u)
GPUCA_B64_ALWAYS constexpr int32_t clz32(u32 x)
GPUCA_B64_ALWAYS constexpr u64 fromFloat(float f)
GPUCA_B64_OP constexpr u64 mul(u64 a, u64 b)
GPUCA_B64_ALWAYS constexpr u32 asu32(float f)
GPUCA_B64_ALWAYS constexpr u64 fromU32(u32 x)
GPUdoubleBinary64 b
GPUdi() o2
Definition TrackTRD.h:39
constexpr int ix
Definition TrackUtils.h:69
unsigned long long ULL
TStopwatch sw