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
58static bool snapFreq(double& firstApprox) {
59 switch (static_cast<int>(firstApprox)) {
60 case 0:
61 if (std::abs(int(firstApprox * 10.0)) <= 1) {
62 firstApprox = 0.0;
63 return true;
64 }
65 break;
66 case 9:
67 if (std::abs(int(firstApprox * 10.0) - 99) <= 1) {
68 firstApprox = 10.0;
69
70 return true;
71 }
72 break;
73 case 3:
74 if (std::abs(int(firstApprox * 10.0) - 33) <= 1) {
75 firstApprox = 3.0 + 1.0 / 3.0;
76 return true;
77 }
78 break;
79 case 6:
80 if (std::abs(int(firstApprox * 10.0) - 66) <= 1) {
81 firstApprox = 6.0 + 2.0 / 3.0;
82 return true;
83 }
84 break;
85 }
86 return false;
87}
88
89static double fallbackTicksPerSecond() {
90 using namespace std::chrono_literals;
91 constexpr double kChronoOverheadBias = 250e-9;
92
93 auto baseStart = std::chrono::high_resolution_clock::now();
94 auto start = rdtscp();
95 std::this_thread::sleep_for(50ms);
96 auto end = rdtscp();
97 auto baseEnd = std::chrono::high_resolution_clock::now();
98
99 auto base = std::chrono::duration<double>(baseEnd - baseStart).count() - kChronoOverheadBias;
100 double firstApprox = (static_cast<double>(end - start)) / base;
101
102 // Try to refine the approximation. In some circumstances we can "snap" the frequency to a very
103 // good guess that is off by less than one part in thousands. Accuracy should already be quite
104 // good in any case, but this allows us to improve in some cases.
105
106 // Get first 3 digits
107 firstApprox *= 1e-7;
108
109 int firstInt = static_cast<int>(firstApprox);
110 firstApprox -= firstInt;
111
112 firstApprox *= 10.0;
113
114 if (!snapFreq(firstApprox)) {
115 int secondInt = static_cast<int>(firstApprox);
116 firstApprox -= secondInt;
117 firstApprox *= 10.0;
118 snapFreq(firstApprox);
119 firstApprox *= 0.1;
120 firstApprox += secondInt;
121 }
122
123 firstApprox *= 0.1;
124
125 firstApprox += firstInt;
126 firstApprox *= 1e7;
127 return firstApprox;
128}
129
130#if defined(__MACH__)
131static double ticksPerSecond() {
132 mach_timebase_info_data_t info;
133 if (mach_timebase_info(&info) != KERN_SUCCESS) {
134 return fallbackTicksPerSecond();
135 }
136 return 1e9 * static_cast<double>(info.denom) / static_cast<double>(info.numer);
137}
138#else
139double ticksPerSecond() {
140 return fallbackTicksPerSecond();
141}
142#endif
143
144double getTime() {
145 static double secondsPerTick = 1.0 / ticksPerSecond();
146 static double startTime = static_cast<double>(rdtscp()) * secondsPerTick;
147
148 double t = static_cast<double>(rdtscp()) * secondsPerTick;
149 return t - startTime;
150}
151#else
152double getTime() {
153 static auto startTime = std::chrono::high_resolution_clock::now();
154 auto cur = std::chrono::high_resolution_clock::now();
155
156 return std::chrono::duration<double>(cur - startTime).count();
157}
158#endif // DISPENSO_HAS_TIMESTAMP
159
160namespace {
161// This should ensure that we initialize the time before main.
162double g_dummyTime = getTime();
163} // namespace
164
165} // namespace dispenso