Pyrogenesis  trunk
timer.h
Go to the documentation of this file.
1 /* Copyright (C) 2022 Wildfire Games.
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * platform-independent high resolution timer
25  */
26 
27 #ifndef INCLUDED_TIMER
28 #define INCLUDED_TIMER
29 
30 #include "lib/config2.h" // CONFIG2_TIMER_ALLOW_RDTSC
31 #include "lib/sysdep/cpu.h" // cpu_AtomicAdd
32 #if ARCH_X86_X64 && CONFIG2_TIMER_ALLOW_RDTSC
33 # include "lib/sysdep/os_cpu.h" // os_cpu_ClockFrequency
34 # include "lib/sysdep/arch/x86_x64/x86_x64.h" // x86_x64::rdtsc
35 #endif
36 
37 #include "lib/utf8.h"
38 
39 #include <cstring>
40 
41 /**
42  * timer_Time will subsequently return values relative to the current time.
43  **/
44 void timer_Init();
45 
46 /**
47  * @return high resolution (> 1 us) timestamp [s].
48  **/
49 double timer_Time();
50 
51 /**
52  * @return resolution [s] of the timer.
53  **/
54 double timer_Resolution();
55 
56 
57 // (allow using XADD (faster than CMPXCHG) in 64-bit builds without casting)
58 #if ARCH_AMD64
59 typedef intptr_t Cycles;
60 #else
61 typedef i64 Cycles;
62 #endif
63 
64 /**
65  * internal helper functions for returning an easily readable
66  * string (i.e. re-scaled to appropriate units)
67  **/
68 std::string StringForSeconds(double seconds);
69 std::string StringForCycles(Cycles cycles);
70 
71 
72 //-----------------------------------------------------------------------------
73 // scope timing
74 
75 /// used by TIMER
77 {
79 public:
80  ScopeTimer(const wchar_t* description)
81  : m_t0(timer_Time()), m_description(description)
82  {
83  }
84 
86  {
87  const double t1 = timer_Time();
88  const std::string elapsedTimeString = StringForSeconds(t1-m_t0);
89  debug_printf("TIMER| %s: %s\n", utf8_from_wstring(m_description).c_str(), elapsedTimeString.c_str());
90  }
91 
92 private:
93  double m_t0;
94  const wchar_t* m_description;
95 };
96 
97 /**
98  * Measures the time taken to execute code up until end of the current scope;
99  * displays it via debug_printf. Can safely be nested.
100  * Useful for measuring time spent in a function or basic block.
101  * <description> must remain valid over the lifetime of this object;
102  * a string literal is safest.
103  *
104  * Example usage:
105  * void func()
106  * {
107  * TIMER(L"description");
108  * // code to be measured
109  * }
110  **/
111 #define TIMER(description) ScopeTimer UID__(description)
112 
113 /**
114  * Measures the time taken to execute code between BEGIN and END markers;
115  * displays it via debug_printf. Can safely be nested.
116  * Useful for measuring several pieces of code within the same function/block.
117  * <description> must remain valid over the lifetime of this object;
118  * a string literal is safest.
119  *
120  * Caveats:
121  * - this wraps the code to be measured in a basic block, so any
122  * variables defined there are invisible to surrounding code.
123  * - the description passed to END isn't inspected; you are responsible for
124  * ensuring correct nesting!
125  *
126  * Example usage:
127  * void func2()
128  * {
129  * // uninteresting code
130  * TIMER_BEGIN(L"description2");
131  * // code to be measured
132  * TIMER_END(L"description2");
133  * // uninteresting code
134  * }
135  **/
136 #define TIMER_BEGIN(description) { ScopeTimer UID__(description)
137 #define TIMER_END(description) }
138 
139 
140 //-----------------------------------------------------------------------------
141 // cumulative timer API
142 
143 // this supplements in-game profiling by providing low-overhead,
144 // high resolution time accounting of specific areas.
145 
146 // since TIMER_ACCRUE et al. are called so often, we try to keep
147 // overhead to an absolute minimum. storing raw tick counts (e.g. CPU cycles
148 // returned by x86_x64::rdtsc) instead of absolute time has two benefits:
149 // - no need to convert from raw->time on every call
150 // (instead, it's only done once when displaying the totals)
151 // - possibly less overhead to querying the time itself
152 // (timer_Time may be using slower time sources with ~3us overhead)
153 //
154 // however, the cycle count is not necessarily a measure of wall-clock time
155 // (see http://www.gamedev.net/reference/programming/features/timing).
156 // therefore, on systems with SpeedStep active, measurements of I/O or other
157 // non-CPU bound activity may be skewed. this is ok because the timer is
158 // only used for profiling; just be aware of the issue.
159 // if this is a problem, disable CONFIG2_TIMER_ALLOW_RDTSC.
160 //
161 // note that overflow isn't an issue either way (63 bit cycle counts
162 // at 10 GHz cover intervals of 29 years).
163 
164 #if ARCH_X86_X64 && CONFIG2_TIMER_ALLOW_RDTSC
165 
166 class TimerUnit
167 {
168 public:
169  void SetToZero()
170  {
171  m_cycles = 0;
172  }
173 
174  void SetFromTimer()
175  {
176  m_cycles = x86_x64::rdtsc();
177  }
178 
179  void AddDifference(TimerUnit t0, TimerUnit t1)
180  {
181  m_cycles += t1.m_cycles - t0.m_cycles;
182  }
183 
184  void AddDifferenceAtomic(TimerUnit t0, TimerUnit t1)
185  {
186  const Cycles delta = t1.m_cycles - t0.m_cycles;
187 #if ARCH_AMD64
188  cpu_AtomicAdd(&m_cycles, delta);
189 #elif ARCH_IA32
190 retry:
191  if(!cpu_CAS64(&m_cycles, m_cycles, m_cycles+delta))
192  goto retry;
193 #else
194 # error "port"
195 #endif
196  }
197 
198  void Subtract(TimerUnit t)
199  {
200  m_cycles -= t.m_cycles;
201  }
202 
203  std::string ToString() const
204  {
205  ENSURE(m_cycles >= 0);
206  return StringForCycles(m_cycles);
207  }
208 
209  double ToSeconds() const
210  {
211  return (double)m_cycles / os_cpu_ClockFrequency();
212  }
213 
214 private:
215  Cycles m_cycles;
216 };
217 
218 #else
219 
221 {
222 public:
223  void SetToZero()
224  {
225  m_seconds = 0.0;
226  }
227 
229  {
230  m_seconds = timer_Time();
231  }
232 
234  {
235  m_seconds += t1.m_seconds - t0.m_seconds;
236  }
237 
239  {
240 retry:
241  i64 oldRepresentation;
242  memcpy(&oldRepresentation, &m_seconds, sizeof(oldRepresentation));
243 
244  const double seconds = m_seconds + t1.m_seconds - t0.m_seconds;
245  i64 newRepresentation;
246  memcpy(&newRepresentation, &seconds, sizeof(newRepresentation));
247 
248  if(!cpu_CAS64((volatile i64*)&m_seconds, oldRepresentation, newRepresentation))
249  goto retry;
250  }
251 
253  {
254  m_seconds -= t.m_seconds;
255  }
256 
257  std::string ToString() const
258  {
259  ENSURE(m_seconds >= 0.0);
260  return StringForSeconds(m_seconds);
261  }
262 
263  double ToSeconds() const
264  {
265  return m_seconds;
266  }
267 
268 private:
269  double m_seconds;
270 };
271 
272 #endif
273 
274 // opaque - do not access its fields!
275 // note: must be defined here because clients instantiate them;
276 // fields cannot be made private due to POD requirement.
278 {
279  TimerUnit sum; // total bill
280 
281  // only store a pointer for efficiency.
282  const wchar_t* description;
283 
285 
286  // how often the timer was billed (helps measure relative
287  // performance of something that is done indeterminately often).
288  intptr_t num_calls;
289 };
290 
291 /**
292  * make the given TimerClient (usually instantiated as static data)
293  * ready for use. returns its address for TIMER_ADD_CLIENT's convenience.
294  * this client's total (which is increased by a BillingPolicy) will be
295  * displayed by timer_DisplayClientTotals.
296  * notes:
297  * - may be called at any time;
298  * - always succeeds (there's no fixed limit);
299  * - free() is not needed nor possible.
300  * - description must remain valid until exit; a string literal is safest.
301  **/
302 TimerClient* timer_AddClient(TimerClient* tc, const wchar_t* description);
303 
304 /**
305  * "allocate" a new TimerClient that will keep track of the total time
306  * billed to it, along with a description string. These are displayed when
307  * timer_DisplayClientTotals is called.
308  * Invoke this at file or function scope; a (static) TimerClient pointer of
309  * name <id> will be defined, which should be passed to TIMER_ACCRUE.
310  **/
311 #define TIMER_ADD_CLIENT(id)\
312  static TimerClient UID__;\
313  static TimerClient* id = timer_AddClient(&UID__, WIDEN(#id))
314 
315 /**
316  * bill the difference between t0 and t1 to the client's total.
317  **/
319 {
320  void operator()(TimerClient* tc, TimerUnit t0, TimerUnit t1) const
321  {
322  tc->sum.AddDifference(t0, t1);
323  tc->num_calls++;
324  }
325 };
326 
327 /**
328  * thread-safe (not used by default due to its higher overhead)
329  * note: we can't just use thread-local variables to avoid
330  * synchronization overhead because we don't have control over all
331  * threads (for accumulating their separate timer copies).
332  **/
334 {
335  void operator()(TimerClient* tc, TimerUnit t0, TimerUnit t1) const
336  {
337  tc->sum.AddDifferenceAtomic(t0, t1);
338  cpu_AtomicAdd(&tc->num_calls, +1);
339  }
340 };
341 
342 /**
343  * display all clients' totals; does not reset them.
344  * typically called at exit.
345  **/
347 
348 
349 /// used by TIMER_ACCRUE
350 template<class BillingPolicy = BillingPolicy_Default>
352 {
354 public:
356  : m_tc(tc)
357  {
358  m_t0.SetFromTimer();
359  }
360 
362  {
363  TimerUnit t1;
364  t1.SetFromTimer();
365  BillingPolicy()(m_tc, m_t0, t1);
366  }
367 
368 private:
371 };
372 
373 /**
374  * Measure the time taken to execute code up until end of the current scope;
375  * bill it to the given TimerClient object. Can safely be nested.
376  * Useful for measuring total time spent in a function or basic block over the
377  * entire program.
378  * `client' is an identifier registered via TIMER_ADD_CLIENT.
379  *
380  * Example usage:
381  * TIMER_ADD_CLIENT(client);
382  *
383  * void func()
384  * {
385  * TIMER_ACCRUE(client);
386  * // code to be measured
387  * }
388  *
389  * [later or at exit]
390  * timer_DisplayClientTotals();
391  **/
392 #define TIMER_ACCRUE(client) ScopeTimerAccrue<> UID__(client)
393 #define TIMER_ACCRUE_ATOMIC(client) ScopeTimerAccrue<BillingPolicy_Atomic> UID__(client)
394 
395 #endif // #ifndef INCLUDED_TIMER
TimerClient * m_tc
Definition: timer.h:370
int64_t i64
Definition: types.h:35
void timer_DisplayClientTotals()
display all clients&#39; totals; does not reset them.
Definition: timer.cpp:192
double os_cpu_ClockFrequency()
Definition: os_cpu.cpp:43
TimerUnit m_t0
Definition: timer.h:369
TimerClient * timer_AddClient(TimerClient *tc, const wchar_t *description)
make the given TimerClient (usually instantiated as static data) ready for use.
Definition: timer.cpp:177
Definition: timer.h:220
ScopeTimer(const wchar_t *description)
Definition: timer.h:80
std::string ToString(const ScriptRequest &rq, JS::MutableHandleValue obj, bool pretty=false)
Convert an object to a UTF-8 encoded string, either with JSON (if pretty == true and there is no JSON...
Definition: JSON.cpp:117
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:212
void timer_Init()
timer_Time will subsequently return values relative to the current time.
Definition: timer.cpp:104
void AddDifferenceAtomic(TimerUnit t0, TimerUnit t1)
Definition: timer.h:238
TimerClient * next
Definition: timer.h:284
intptr_t cpu_AtomicAdd(volatile intptr_t *location, intptr_t increment)
add a signed value to a variable without the possibility of interference from other threads/CPUs...
Definition: aarch64.cpp:31
std::string StringForCycles(Cycles cycles)
Definition: timer.cpp:225
Definition: timer.h:277
NONCOPYABLE(ScopeTimer)
bill the difference between t0 and t1 to the client&#39;s total.
Definition: timer.h:318
void AddDifference(TimerUnit t0, TimerUnit t1)
Definition: timer.h:233
double timer_Time()
Definition: timer.cpp:130
double timer_Resolution()
Definition: timer.cpp:158
std::string ToString() const
Definition: timer.h:257
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:290
thread-safe (not used by default due to its higher overhead) note: we can&#39;t just use thread-local var...
Definition: timer.h:333
u64 rdtsc()
Definition: x86_x64.cpp:373
void operator()(TimerClient *tc, TimerUnit t0, TimerUnit t1) const
Definition: timer.h:320
bool cpu_CAS64(volatile i64 *location, i64 expected, i64 newValue)
Definition: aarch64.cpp:41
TimerUnit sum
Definition: timer.h:279
void debug_printf(const char *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:148
used by TIMER
Definition: timer.h:76
void SetFromTimer()
Definition: timer.h:228
const wchar_t * m_description
Definition: timer.h:94
double m_t0
Definition: timer.h:93
i64 Cycles
Definition: timer.h:61
const wchar_t * description
Definition: timer.h:282
void Subtract(TimerUnit t)
Definition: timer.h:252
used by TIMER_ACCRUE
Definition: timer.h:351
std::string StringForSeconds(double seconds)
internal helper functions for returning an easily readable string (i.e.
Definition: timer.cpp:209
void SetToZero()
Definition: timer.h:223
double m_seconds
Definition: timer.h:269
~ScopeTimerAccrue()
Definition: timer.h:361
double ToSeconds() const
Definition: timer.h:263
~ScopeTimer()
Definition: timer.h:85
void operator()(TimerClient *tc, TimerUnit t0, TimerUnit t1) const
Definition: timer.h:335
intptr_t num_calls
Definition: timer.h:288
ScopeTimerAccrue(TimerClient *tc)
Definition: timer.h:355