HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
time_stamp_utc.hpp
1// Copyright Take Vos 2021.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "chrono.hpp"
8#include "time_stamp_count.hpp"
9#include "../utility/utility.hpp"
10#include "../concurrency/concurrency.hpp"
11#include "../macros.hpp"
12#include <array>
13#include <atomic>
14#include <thread>
15
16
17
18namespace hi::inline v1 {
19
26 [[nodiscard]] static utc_nanoseconds now(time_stamp_count& tsc)
27 {
28 auto shortest_diff = std::numeric_limits<uint64_t>::max();
29 time_stamp_count shortest_tsc;
30 utc_nanoseconds shortest_tp;
31
32 // With three samples gathered on the same CPU we should
33 // have a TSC/UTC/TSC combination that was run inside a single time-slice.
34 for (auto i = 0; i != 10; ++i) {
35 hilet tmp_tsc1 = time_stamp_count::now();
36 hilet tmp_tp = std::chrono::utc_clock::now();
37 hilet tmp_tsc2 = time_stamp_count::now();
38
39 if (tmp_tsc1.cpu_id() != tmp_tsc2.cpu_id()) {
40 throw os_error("CPU Switch detected during get_sample(), which should never happen");
41 }
42
43 if (tmp_tsc1.count() > tmp_tsc2.count()) {
44 // TSC skipped backwards, this may happen when the TSC of multiple
45 // CPUs get synchronized with each other.
46 // For example when waking up from sleep.
47 continue;
48 }
49
50 hilet diff = tmp_tsc2.count() - tmp_tsc1.count();
51
52 if (diff < shortest_diff) {
53 shortest_diff = diff;
54 shortest_tp = tmp_tp;
55 shortest_tsc = tmp_tsc1 + (diff / 2);
56 }
57 }
58
59 if (shortest_diff == std::numeric_limits<uint64_t>::max()) {
60 throw os_error("Unable to get TSC sample.");
61 }
62
63 tsc = shortest_tsc;
64 return shortest_tp;
65 }
66
75 [[nodiscard]] static utc_nanoseconds make(time_stamp_count const& tsc) noexcept
76 {
77 auto i = tsc.cpu_id();
78 if (i >= 0) {
79 hilet tsc_epoch = tsc_epochs[i].load(std::memory_order::relaxed);
80 if (tsc_epoch != utc_nanoseconds{}) {
81 return tsc_epoch + tsc.time_since_epoch();
82 }
83 }
84
85 // Fallback.
86 hilet ref_tp = std::chrono::utc_clock::now();
87 hilet ref_tsc = time_stamp_count::now();
88 hilet diff_ns = ref_tsc.time_since_epoch() - tsc.time_since_epoch();
89 return ref_tp - diff_ns;
90 }
91
94 static bool start_subsystem() noexcept
95 {
96 return hi::start_subsystem(global_state_type::time_stamp_utc_is_running, init_subsystem, deinit_subsystem);
97 }
98
101 static void stop_subsystem() noexcept
102 {
103 return hi::stop_subsystem(deinit_subsystem);
104 }
105
112 static void adjust_for_drift() noexcept;
113
114private:
115 static inline std::jthread subsystem_thread;
116 static inline unfair_mutex mutex;
117 static inline std::array<std::atomic<utc_nanoseconds>, maximum_num_cpus> tsc_epochs = {};
118
119 static void subsystem_proc_frequency_calibration(std::stop_token stop_token)
120 {
121 using namespace std::chrono_literals;
122
123 // Calibrate the TSC frequency to within 1 ppm.
124 // A 1s measurement already brings is to about 1ppm. We are
125 // going to be taking average of the IQR of 11 samples, just
126 // in case there are UTC clock adjustment made during the measurement.
127
128 std::array<uint64_t, 16> frequencies;
129 for (auto i = 0; i != frequencies.size();) {
130 hilet f = time_stamp_count::measure_frequency(1s);
131 if (f != 0) {
132 frequencies[i] = f;
133 ++i;
134 }
135
136 if (stop_token.stop_requested()) {
137 return;
138 }
139 }
140 std::ranges::sort(frequencies);
141 hilet iqr_size = frequencies.size() / 2;
142 hilet iqr_first = std::next(frequencies.cbegin(), frequencies.size() / 4);
143 hilet iqr_last = std::next(iqr_first, iqr_size);
144 hilet frequency = std::accumulate(iqr_first, iqr_last, uint64_t{0}) / iqr_size;
145
146 time_stamp_count::set_frequency(frequency);
147 }
148 static void subsystem_proc(std::stop_token stop_token)
149 {
150 using namespace std::chrono_literals;
151
152 set_thread_name("time_stamp_utc");
153 subsystem_proc_frequency_calibration(stop_token);
154
155 hilet process_cpu_mask = process_affinity_mask();
156
157 std::size_t next_cpu = 0;
158 while (not stop_token.stop_requested()) {
159 hilet current_cpu = advance_thread_affinity(next_cpu);
160
162 hilet lock = std::scoped_lock(time_stamp_utc::mutex);
163
164 time_stamp_count tsc;
165 hilet tp = time_stamp_utc::now(tsc);
166 hi_assert(tsc.cpu_id() == narrow_cast<ssize_t>(current_cpu));
167
168 tsc_epochs[current_cpu].store(tp - tsc.time_since_epoch(), std::memory_order::relaxed);
169 }
170 }
171
174 static bool init_subsystem() noexcept
175 {
176 time_stamp_utc::subsystem_thread = std::jthread{subsystem_proc};
177 return true;
178 }
179
182 static void deinit_subsystem() noexcept
183 {
184 if (global_state_disable(global_state_type::time_stamp_utc_is_running)) {
185 if (time_stamp_utc::subsystem_thread.joinable()) {
186 time_stamp_utc::subsystem_thread.request_stop();
187 time_stamp_utc::subsystem_thread.join();
188 }
189 }
190 }
191
192 [[nodiscard]] static std::size_t find_cpu_id(uint32_t cpu_id) noexcept;
193};
194
195constexpr std::string format_engineering(std::chrono::nanoseconds duration)
196{
197 using namespace std::chrono_literals;
198
199 if (duration >= 1s) {
200 return std::format("{:.3g}s ", narrow_cast<double>(duration / 1ns) / 1'000'000'000);
201 } else if (duration >= 1ms) {
202 return std::format("{:.3g}ms", narrow_cast<double>(duration / 1ns) / 1'000'000);
203 } else if (duration >= 1us) {
204 return std::format("{:.3g}us", narrow_cast<double>(duration / 1ns) / 1'000);
205 } else {
206 return std::format("{:.3g}ns", narrow_cast<double>(duration / 1ns));
207 }
208}
209
210} // namespace hi::inline v1
std::vector< bool > process_affinity_mask()
Get the current process CPU affinity mask.
void set_thread_name(std::string_view name) noexcept
Set the name of the current thread.
T::value_type start_subsystem(T &check_variable, typename T::value_type off_value, typename T::value_type(*init_function)(), void(*deinit_function)())
Start a sub-system.
Definition subsystem.hpp:116
bool global_state_disable(global_state_type subsystem, std::memory_order order=std::memory_order::seq_cst) noexcept
Disable a subsystem.
Definition global_state.hpp:247
std::size_t advance_thread_affinity(std::size_t &cpu) noexcept
Advance thread affinity to the next CPU.
Definition thread_intf.hpp:122
void stop_subsystem(void(*deinit_function)())
Stop a sub-system.
Definition subsystem.hpp:202
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:16
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
Since Window's 10 QueryPerformanceCounter() counts at only 10MHz which is too low to measure performa...
Definition time_stamp_count.hpp:31
Timestamp.
Definition time_stamp_utc.hpp:22
static void stop_subsystem() noexcept
This will stop the calibration subsystem.
Definition time_stamp_utc.hpp:101
static bool start_subsystem() noexcept
This will start the calibration subsystem.
Definition time_stamp_utc.hpp:94
static utc_nanoseconds make(time_stamp_count const &tsc) noexcept
Make a time point from a time stamp count.
Definition time_stamp_utc.hpp:75
static utc_nanoseconds now(time_stamp_count &tsc)
Get the current time and TSC value.
Definition time_stamp_utc.hpp:26
static void adjust_for_drift() noexcept
A calibration step which will drift the per-cpu tsc-offset.
T accumulate(T... args)
T cbegin(T... args)
T lock(T... args)
T max(T... args)
T next(T... args)
T size(T... args)
T sleep_for(T... args)