1/*******************************************************************************
2 Copyright (c) The Taichi Authors (2016- ). All Rights Reserved.
3 The use of this software is governed by the LICENSE file.
4*******************************************************************************/
5
6#include "taichi/system/timer.h"
7
8#ifndef _WIN64
9
10#include <unistd.h>
11
12#endif
13
14namespace taichi {
15
16using namespace std;
17
18std::map<std::string, std::pair<double, int>> Time::Timer::memo;
19
20std::map<std::string, double> Time::FPSCounter::last_refresh;
21std::map<std::string, int> Time::FPSCounter::counter;
22
23#if defined(TI_PLATFORM_UNIX)
24
25double Time::get_time() {
26 struct timeval tv;
27 gettimeofday(&tv, nullptr);
28 return tv.tv_sec + 1e-6 * tv.tv_usec;
29}
30
31#else
32#include <intrin.h>
33#pragma intrinsic(__rdtsc)
34double Time::get_time() {
35 // https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
36 LARGE_INTEGER EndingTime, ElapsedMicroseconds;
37 LARGE_INTEGER Frequency;
38
39 QueryPerformanceFrequency(&Frequency);
40
41 // Activity to be timed
42
43 QueryPerformanceCounter(&EndingTime);
44 ElapsedMicroseconds.QuadPart = EndingTime.QuadPart;
45
46 ElapsedMicroseconds.QuadPart *= 1000000;
47 ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
48 return (double)ElapsedMicroseconds.QuadPart / 1000000.0;
49
50 /*
51 FILETIME tm;
52 GetSystemTimeAsFileTime(&tm);
53 unsigned long long t = ((ULONGLONG)tm.dwHighDateTime << 32) |
54 (ULONGLONG)tm.dwLowDateTime;
55 return (double)t / 10000000.0;
56*/
57}
58#endif
59
60#ifdef _WIN64
61#include <Windows.h>
62
63namespace {
64void win_usleep(double us) {
65 using us_t = chrono::duration<double, std::micro>;
66 auto start = chrono::high_resolution_clock::now();
67 do {
68 // still little possible to release cpu.
69 // Note:
70 // https://docs.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-sleep
71 Sleep(0);
72 } while ((us_t(chrono::high_resolution_clock::now() - start).count()) < us);
73}
74
75void win_msleep(DWORD ms) {
76 if (ms == 0)
77 Sleep(0);
78 else {
79 HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
80 timeSetEvent(ms, 1, (LPTIMECALLBACK)hEvent, 0,
81 TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);
82 WaitForSingleObject(hEvent, INFINITE);
83 CloseHandle(hEvent);
84 }
85}
86} // namespace
87#endif
88
89void Time::usleep(double us) {
90#ifdef _WIN64
91 // use win_usleep for accuracy.
92 if (us < 999)
93 win_usleep(us);
94 // use win_msleep to release cpu, precision < 1ms
95 else
96 win_msleep(DWORD(us * 1e-3));
97#else
98 ::usleep(us);
99#endif
100}
101
102void Time::msleep(double ms) {
103#ifdef _WIN64
104 win_msleep(DWORD(ms));
105#else
106 ::usleep(ms * 1e3_f64);
107#endif
108}
109
110void Time::sleep(double s) {
111 Time::usleep(s * 1e6_f64);
112}
113
114void Time::wait_until(double t) {
115 // microsecond (us) sleep on Windows... sadly.
116 double dt;
117 if (t < Time::get_time()) {
118 return;
119 }
120 do { // use system-provided sleep for large scale sleeping:
121 dt = t - Time::get_time();
122 if (dt <= 0) {
123 return;
124 }
125#ifdef _WIN64
126 Time::sleep(dt * 0.5);
127#else
128 Time::sleep(dt * (dt < 4e-2_f64 ? 0.02 : 0.4));
129#endif
130 } while (dt > 2e-4_f64); // until dt <= 200us
131
132 // use an EBFE loop for small scale waiting:
133 while (Time::get_time() < t - 1e-6_f64)
134 ; // until dt <= 1us
135}
136
137double Time::Timer::get_time() {
138 return Time::get_time();
139}
140
141void Time::Timer::print_record(const char *left,
142 double elapsed,
143 double average) {
144 if (elapsed < 1e-3) {
145 printf("%s ==> %6.3f us ~ %6.3f us\n", left, elapsed * 1e6, average * 1e6);
146 } else {
147 printf("%s ==> %6.3f ms ~ %6.3f ms\n", left, elapsed * 1e3, average * 1e3);
148 }
149}
150
151void Time::Timer::output() {
152 if (have_output) {
153 return;
154 } else {
155 have_output = true;
156 }
157 double elapsed = get_time() - this->start_time;
158 std::string left = this->name;
159 if (left.size() < 60) {
160 left += std::string(60 - left.size(), '-');
161 }
162 if (memo.find(name) == memo.end()) {
163 memo.insert(make_pair(name, make_pair(0.0, 0)));
164 }
165 pair<double, int> memo_record = memo[name];
166 memo_record.first += elapsed;
167 memo_record.second += 1;
168 memo[name] = memo_record;
169 double avg = memo_record.first / memo_record.second;
170 this->print_record(left.c_str(), elapsed, avg);
171}
172
173double Time::TickTimer::get_time() {
174 return Time::get_time();
175}
176
177void Time::TickTimer::print_record(const char *left,
178 double elapsed,
179 double average) {
180 string unit;
181 double measurement;
182 if (elapsed < 1e3) {
183 measurement = 1.0;
184 unit = "cycles";
185 } else if (elapsed < 1e6) {
186 measurement = 1e3;
187 unit = "K cycles";
188 } else if (elapsed < 1e9) {
189 measurement = 1e6;
190 unit = "M cycles";
191 } else {
192 measurement = 1e9;
193 unit = "G cycles";
194 }
195 printf("%s ==> %4.2f %s ~ %4.2f %s\n", left, elapsed / measurement,
196 unit.c_str(), average / measurement, unit.c_str());
197}
198
199Time::Timer::Timer(std::string name) {
200 this->name = name;
201 this->start_time = get_time();
202 this->have_output = false;
203}
204
205Time::TickTimer::TickTimer(std::string name) {
206 this->name = name;
207 this->start_time = get_time();
208 this->have_output = false;
209}
210
211// Windows
212#ifdef _WIN32
213
214#include <intrin.h>
215uint64 Time::get_cycles() {
216 return __rdtsc();
217}
218
219// Linux/GCC
220#else
221
222uint64 Time::get_cycles() {
223#if defined(TI_ARCH_x64) && !(defined(__arm64__) || defined(__aarch64__))
224 unsigned int lo, hi;
225 __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi));
226 return ((uint64)hi << 32) | lo;
227#else
228 TI_WARN("get_cycles is not implemented in this platform. Returning 0.");
229 return 0;
230#endif
231}
232
233#endif
234
235} // namespace taichi
236