Pyrogenesis HEAD
Pyrogenesis, a RTS Engine
timer.h
Go to the documentation of this file.
1/* Copyright (C) 2024 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#if ARCH_X86_X64 && CONFIG2_TIMER_ALLOW_RDTSC
32# include "lib/sysdep/os_cpu.h" // os_cpu_ClockFrequency
33# include "lib/sysdep/arch/x86_x64/x86_x64.h" // x86_x64::rdtsc
34#endif
35
36#include "lib/utf8.h"
37
38#include <cstring>
39
40/**
41 * timer_Time will subsequently return values relative to the current time.
42 **/
43void timer_Init();
44
45/**
46 * @return high resolution (> 1 us) timestamp [s].
47 **/
48double timer_Time();
49
50/**
51 * @return resolution [s] of the timer.
52 **/
53double timer_Resolution();
54
55
56// (allow using XADD (faster than CMPXCHG) in 64-bit builds without casting)
57#if ARCH_AMD64
58typedef intptr_t Cycles;
59#else
60typedef i64 Cycles;
61#endif
62
63/**
64 * internal helper functions for returning an easily readable
65 * string (i.e. re-scaled to appropriate units)
66 **/
67std::string StringForSeconds(double seconds);
68std::string StringForCycles(Cycles cycles);
69
70
71//-----------------------------------------------------------------------------
72// scope timing
73
74/// used by TIMER
76{
78public:
79 ScopeTimer(const wchar_t* description)
80 : m_t0(timer_Time()), m_description(description)
81 {
82 }
83
85 {
86 const double t1 = timer_Time();
87 const std::string elapsedTimeString = StringForSeconds(t1-m_t0);
88 debug_printf("TIMER| %s: %s\n", utf8_from_wstring(m_description).c_str(), elapsedTimeString.c_str());
89 }
90
91private:
92 double m_t0;
93 const wchar_t* m_description;
94};
95
96/**
97 * Measures the time taken to execute code up until end of the current scope;
98 * displays it via debug_printf. Can safely be nested.
99 * Useful for measuring time spent in a function or basic block.
100 * <description> must remain valid over the lifetime of this object;
101 * a string literal is safest.
102 *
103 * Example usage:
104 * void func()
105 * {
106 * TIMER(L"description");
107 * // code to be measured
108 * }
109 **/
110#define TIMER(description) ScopeTimer UID__(description)
111
112/**
113 * Measures the time taken to execute code between BEGIN and END markers;
114 * displays it via debug_printf. Can safely be nested.
115 * Useful for measuring several pieces of code within the same function/block.
116 * <description> must remain valid over the lifetime of this object;
117 * a string literal is safest.
118 *
119 * Caveats:
120 * - this wraps the code to be measured in a basic block, so any
121 * variables defined there are invisible to surrounding code.
122 * - the description passed to END isn't inspected; you are responsible for
123 * ensuring correct nesting!
124 *
125 * Example usage:
126 * void func2()
127 * {
128 * // uninteresting code
129 * TIMER_BEGIN(L"description2");
130 * // code to be measured
131 * TIMER_END(L"description2");
132 * // uninteresting code
133 * }
134 **/
135#define TIMER_BEGIN(description) { ScopeTimer UID__(description)
136#define TIMER_END(description) }
137
138
139//-----------------------------------------------------------------------------
140// cumulative timer API
141
142// this supplements in-game profiling by providing low-overhead,
143// high resolution time accounting of specific areas.
144
145// since TIMER_ACCRUE et al. are called so often, we try to keep
146// overhead to an absolute minimum. storing raw tick counts (e.g. CPU cycles
147// returned by x86_x64::rdtsc) instead of absolute time has two benefits:
148// - no need to convert from raw->time on every call
149// (instead, it's only done once when displaying the totals)
150// - possibly less overhead to querying the time itself
151// (timer_Time may be using slower time sources with ~3us overhead)
152//
153// however, the cycle count is not necessarily a measure of wall-clock time
154// (see http://www.gamedev.net/reference/programming/features/timing).
155// therefore, on systems with SpeedStep active, measurements of I/O or other
156// non-CPU bound activity may be skewed. this is ok because the timer is
157// only used for profiling; just be aware of the issue.
158// if this is a problem, disable CONFIG2_TIMER_ALLOW_RDTSC.
159//
160// note that overflow isn't an issue either way (63 bit cycle counts
161// at 10 GHz cover intervals of 29 years).
162
163#if ARCH_X86_X64 && CONFIG2_TIMER_ALLOW_RDTSC
164
165class TimerUnit
166{
167public:
168 void SetToZero()
169 {
170 m_cycles = 0;
171 }
172
173 void SetFromTimer()
174 {
175 m_cycles = x86_x64::rdtsc();
176 }
177
179 {
180 m_cycles += t1.m_cycles - t0.m_cycles;
181 }
182
183 void Subtract(TimerUnit t)
184 {
185 m_cycles -= t.m_cycles;
186 }
187
188 std::string ToString() const
189 {
190 ENSURE(m_cycles >= 0);
191 return StringForCycles(m_cycles);
192 }
193
194 double ToSeconds() const
195 {
196 return (double)m_cycles / os_cpu_ClockFrequency();
197 }
198
199private:
200 Cycles m_cycles;
201};
202
203#else
204
206{
207public:
209 {
210 m_seconds = 0.0;
211 }
212
214 {
216 }
217
219 {
220 m_seconds += t1.m_seconds - t0.m_seconds;
221 }
222
224 {
225 m_seconds -= t.m_seconds;
226 }
227
228 std::string ToString() const
229 {
230 ENSURE(m_seconds >= 0.0);
232 }
233
234 double ToSeconds() const
235 {
236 return m_seconds;
237 }
238
239private:
240 double m_seconds;
241};
242
243#endif
244
245// opaque - do not access its fields!
246// note: must be defined here because clients instantiate them;
247// fields cannot be made private due to POD requirement.
249{
250 TimerUnit sum; // total bill
251
252 // only store a pointer for efficiency.
253 const wchar_t* description;
254
256
257 // how often the timer was billed (helps measure relative
258 // performance of something that is done indeterminately often).
259 intptr_t num_calls;
260};
261
262/**
263 * make the given TimerClient (usually instantiated as static data)
264 * ready for use. returns its address for TIMER_ADD_CLIENT's convenience.
265 * this client's total (which is increased by a BillingPolicy) will be
266 * displayed by timer_DisplayClientTotals.
267 * notes:
268 * - may be called at any time;
269 * - always succeeds (there's no fixed limit);
270 * - free() is not needed nor possible.
271 * - description must remain valid until exit; a string literal is safest.
272 **/
273TimerClient* timer_AddClient(TimerClient* tc, const wchar_t* description);
274
275/**
276 * "allocate" a new TimerClient that will keep track of the total time
277 * billed to it, along with a description string. These are displayed when
278 * timer_DisplayClientTotals is called.
279 * Invoke this at file or function scope; a (static) TimerClient pointer of
280 * name <id> will be defined, which should be passed to TIMER_ACCRUE.
281 **/
282#define TIMER_ADD_CLIENT(id)\
283 static TimerClient UID__;\
284 static TimerClient* id = timer_AddClient(&UID__, WIDEN(#id))
285
286/**
287 * display all clients' totals; does not reset them.
288 * typically called at exit.
289 **/
291
292
293/// used by TIMER_ACCRUE
295{
297public:
299 : m_tc(tc)
300 {
302 }
303
305 {
306 TimerUnit t1;
307 t1.SetFromTimer();
309 ++m_tc->num_calls;
310 }
311
312private:
315};
316
317/**
318 * Measure the time taken to execute code up until end of the current scope;
319 * bill it to the given TimerClient object. Can safely be nested.
320 * Useful for measuring total time spent in a function or basic block over the
321 * entire program.
322 * `client' is an identifier registered via TIMER_ADD_CLIENT.
323 *
324 * Example usage:
325 * TIMER_ADD_CLIENT(client);
326 *
327 * void func()
328 * {
329 * TIMER_ACCRUE(client);
330 * // code to be measured
331 * }
332 *
333 * [later or at exit]
334 * timer_DisplayClientTotals();
335 **/
336#define TIMER_ACCRUE(client) ScopeTimerAccrue UID__(client)
337
338#endif // #ifndef INCLUDED_TIMER
used by TIMER_ACCRUE
Definition: timer.h:295
TimerUnit m_t0
Definition: timer.h:313
NONCOPYABLE(ScopeTimerAccrue)
ScopeTimerAccrue(TimerClient *tc)
Definition: timer.h:298
~ScopeTimerAccrue()
Definition: timer.h:304
TimerClient * m_tc
Definition: timer.h:314
used by TIMER
Definition: timer.h:76
NONCOPYABLE(ScopeTimer)
const wchar_t * m_description
Definition: timer.h:93
double m_t0
Definition: timer.h:92
~ScopeTimer()
Definition: timer.h:84
ScopeTimer(const wchar_t *description)
Definition: timer.h:79
Definition: timer.h:206
double ToSeconds() const
Definition: timer.h:234
void SetToZero()
Definition: timer.h:208
void Subtract(TimerUnit t)
Definition: timer.h:223
std::string ToString() const
Definition: timer.h:228
double m_seconds
Definition: timer.h:240
void AddDifference(TimerUnit t0, TimerUnit t1)
Definition: timer.h:218
void SetFromTimer()
Definition: timer.h:213
void debug_printf(const char *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:143
#define ENSURE(expr)
ensure the expression <expr> evaluates to non-zero.
Definition: debug.h:277
u64 rdtsc()
Definition: x86_x64.cpp:373
double os_cpu_ClockFrequency()
Definition: os_cpu.cpp:43
Definition: timer.h:249
const wchar_t * description
Definition: timer.h:253
intptr_t num_calls
Definition: timer.h:259
TimerClient * next
Definition: timer.h:255
TimerUnit sum
Definition: timer.h:250
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
std::string StringForCycles(Cycles cycles)
Definition: timer.cpp:225
double timer_Time()
Definition: timer.cpp:130
void timer_Init()
timer_Time will subsequently return values relative to the current time.
Definition: timer.cpp:104
double timer_Resolution()
Definition: timer.cpp:158
std::string StringForSeconds(double seconds)
internal helper functions for returning an easily readable string (i.e.
Definition: timer.cpp:209
i64 Cycles
Definition: timer.h:60
void timer_DisplayClientTotals()
display all clients' totals; does not reset them.
Definition: timer.cpp:192
int64_t i64
Definition: types.h:35
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:212