dispenso
A library for task parallelism
 
Loading...
Searching...
No Matches
timing.cpp
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#include <dispenso/timing.h>
9
10#include <chrono>
11#include <cmath>
12
13#if defined(_MSC_VER)
14#include <intrin.h>
15#endif // _MSC_VER
16
17#if defined(_WIN32)
18#include <Windows.h>
19#endif // _WIN32
20
21#if defined(__MACH__)
22#include <mach/mach.h>
23#include <mach/mach_time.h>
24#endif // __MACH__
25
26namespace dispenso {
27namespace {
28#if defined(__x86_64__) || defined(_M_AMD64)
29#define DISPENSO_HAS_TIMESTAMP
30#if defined(_MSC_VER)
31inline uint64_t rdtscp() {
32 uint32_t ui;
33 return __rdtscp(&ui);
34}
35
36#else
37inline uint64_t rdtscp() {
38 uint32_t lo, hi;
39 __asm__ volatile("rdtscp"
40 : /* outputs */ "=a"(lo), "=d"(hi)
41 : /* no inputs */
42 : /* clobbers */ "%rcx");
43 return (uint64_t)lo | (((uint64_t)hi) << 32);
44}
45#endif // OS
46#elif (defined(__GNUC__) || defined(__clang__)) && defined(__aarch64__)
47#define DISPENSO_HAS_TIMESTAMP
48uint64_t rdtscp(void) {
49 uint64_t val;
50 __asm__ volatile("mrs %0, cntvct_el0" : "=r"(val));
51 return val;
52}
53#endif // ARCH
54} // namespace
55
56#if defined(DISPENSO_HAS_TIMESTAMP)
57
58#if !defined(__aarch64__)
59
60static bool snapFreq(double& firstApprox) {
61 switch (static_cast<int>(firstApprox)) {
62 case 0:
63 if (std::abs(int(firstApprox * 10.0)) <= 1) {
64 firstApprox = 0.0;
65 return true;
66 }
67 break;
68 case 9:
69 if (std::abs(int(firstApprox * 10.0) - 99) <= 1) {
70 firstApprox = 10.0;
71
72 return true;
73 }
74 break;
75 case 3:
76 if (std::abs(int(firstApprox * 10.0) - 33) <= 1) {
77 firstApprox = 3.0 + 1.0 / 3.0;
78 return true;
79 }
80 break;
81 case 6:
82 if (std::abs(int(firstApprox * 10.0) - 66) <= 1) {
83 firstApprox = 6.0 + 2.0 / 3.0;
84 return true;
85 }
86 break;
87 }
88 return false;
89}
90
91static double fallbackTicksPerSecond() {
92 using namespace std::chrono_literals;
93 constexpr double kChronoOverheadBias = 250e-9;
94
95 auto baseStart = std::chrono::high_resolution_clock::now();
96 auto start = rdtscp();
97 std::this_thread::sleep_for(50ms);
98 auto end = rdtscp();
99 auto baseEnd = std::chrono::high_resolution_clock::now();
100
101 auto base = std::chrono::duration<double>(baseEnd - baseStart).count() - kChronoOverheadBias;
102 double firstApprox = (static_cast<double>(end - start)) / base;
103
104 // Try to refine the approximation. In some circumstances we can "snap" the frequency to a very
105 // good guess that is off by less than one part in thousands. Accuracy should already be quite
106 // good in any case, but this allows us to improve in some cases.
107
108 // Get first 3 digits
109 firstApprox *= 1e-7;
110
111 int firstInt = static_cast<int>(firstApprox);
112 firstApprox -= firstInt;
113
114 firstApprox *= 10.0;
115
116 if (!snapFreq(firstApprox)) {
117 int secondInt = static_cast<int>(firstApprox);
118 firstApprox -= secondInt;
119 firstApprox *= 10.0;
120 snapFreq(firstApprox);
121 firstApprox *= 0.1;
122 firstApprox += secondInt;
123 }
124
125 firstApprox *= 0.1;
126
127 firstApprox += firstInt;
128 firstApprox *= 1e7;
129 return firstApprox;
130}
131#endif // !__aarch64__
132
133#if defined(__aarch64__)
134static double ticksPerSecond() {
135 uint64_t val;
136 __asm__ volatile("mrs %0, cntfrq_el0" : "=r"(val));
137 return static_cast<double>(val);
138}
139#elif defined(__MACH__)
140static double ticksPerSecond() {
141 mach_timebase_info_data_t info;
142 if (mach_timebase_info(&info) != KERN_SUCCESS) {
143 return fallbackTicksPerSecond();
144 }
145 return 1e9 * static_cast<double>(info.denom) / static_cast<double>(info.numer);
146}
147#else
148double ticksPerSecond() {
149 return fallbackTicksPerSecond();
150}
151#endif
152
153double getTime() {
154 static double secondsPerTick = 1.0 / ticksPerSecond();
155 static double startTime = static_cast<double>(rdtscp()) * secondsPerTick;
156
157 double t = static_cast<double>(rdtscp()) * secondsPerTick;
158 return t - startTime;
159}
160#else
161double getTime() {
162 static auto startTime = std::chrono::high_resolution_clock::now();
163 auto cur = std::chrono::high_resolution_clock::now();
164
165 return std::chrono::duration<double>(cur - startTime).count();
166}
167#endif // DISPENSO_HAS_TIMESTAMP
168
169namespace {
170// This should ensure that we initialize the time before main.
171double g_dummyTime = getTime();
172} // namespace
173
174} // namespace dispenso