1 ///////////////////////////////////////////////////////////////////////////////
2 // EAStopwatch.h
3 //
4 // Copyright (c) Electronic Arts. All rights reserved.
5 ///////////////////////////////////////////////////////////////////////////////
6
7 #ifndef EASTDC_EASTOPWATCH_H
8 #define EASTDC_EASTOPWATCH_H
9
10 #define EASTDC_API
11 #define EASTDC_LOCAL
12
13 #include <EABase/eabase.h>
14
15 #include <chrono>
16
17 namespace EA {
18 namespace StdC {
19
20
21 class EASTDC_API Stopwatch
22 {
23 public:
24 /// Units
25 /// Defines common timing units plus a user-definable set of units.
26 enum Units
27 {
28 kUnitsCycles = 0, /// Stopwatch clock ticks. May or may not match CPU clock ticks 1:1, depending on your hardware and operating system. Some CPUs' low level cycle count instruction counts every 16 cycles instead of every cycle.
29 kUnitsCPUCycles = 1, /// CPU clock ticks (or similar equivalent for the platform). Not recommended for use in shipping softare as many systems alter their CPU frequencies at runtime.
30 kUnitsNanoseconds = 2, /// For a 1GHz processor, 1 nanosecond is the same as 1 clock tick.
31 kUnitsMilliseconds = 4, /// For a 1GHz processor, 1 millisecond is the same as 1,000,000 clock ticks.
32 kUnitsMicroseconds = 6,
33 kUnitsSeconds = 7,
34 kUnitsMinutes = 8,
35 kUnitsUserDefined = 1000,
36 };
37
38 public:
39 /// Stopwatch
40 /// Constructor for Stopwatch, allows user to specify units.
41 /// If units are kUnitsUserDefined, you'll need to either subclass
42 /// Stopwatch and implement GetUserDefinedStopwatchCycle or call
43 /// SetUserDefinedUnitsToStopwatchCyclesRatio in order to allow it
44 /// to know how to convert units.
45 explicit Stopwatch(int nUnits = kUnitsCycles, bool bStartImmediately = false)
46 : mnStartTime(0)
47 , mnTotalElapsedTime(0)
48 , mnUnits(nUnits)
49 , mfStopwatchCyclesToUnitsCoefficient(1.f)
50 {
51 if(bStartImmediately)
52 Start();
53 }
54
55 /// Start
56 /// Starts the stopwatch. Continues where it was last stopped.
57 /// Does nothing if the stopwatch is already started.
58 void Start();
59
60 /// Stop
61 /// Stops the stopwatch it it was running and retaines the elasped time.
62 void Stop();
63
64 /// Restart
65 void Restart();
66
67 /// GetUnits
68 int GetUnits();
69
70 /// GetElapsedTimeFloat
71 float GetElapsedTimeFloat() const;
72
73 /// GetElapsedTime
74 /// Gets the elapsed time, which properly takes into account any
75 /// intervening stops and starts. Works properly whether the stopwatch
76 /// is running or not.
77 uint64_t GetElapsedTime() const;
78
79 /// GetStopwatchCycle
80 /// Gets the current stopwatch cycle on the current machine.
81 /// Note that a stopwatch cyle may or may not be the same thing
82 /// as a CPU cycle. We provide the distinction between stopwatch
83 /// cycles and CPU cycles in order to accomodate platforms (e.g.
84 /// desktop platforms) in which CPU cycle counting is unreliable.
85 static uint64_t GetStopwatchCycle();
86
87 /// GetCPUCycle
88 /// Gets the current CPU-based timer cycle on the current processor
89 /// (if in a multiprocessor system). Note that this doesn't necessarily
90 /// get the actual machine CPU clock cycle; rather it returns the
91 /// CPU-based timer cycle. One some platforms the CPU-based timer is
92 /// a 1:1 relation to the CPU clock, while on others it is some multiple
93 /// of it.
94 /// Note that on some systems you can't rely on kUnitsCycles being consistent
95 /// at runtime, especially on x86 PCs with their multiple desynchronized CPUs
96 /// and variable runtime clock speed.
97 static uint64_t GetCPUCycle();
98
99 /// GetUnitsPerCPUCycle
100 static double GetUnitsPerCPUCycle(EA::StdC::Stopwatch::Units);
101
102 /// GetUnitsPerStopwatchCycle
103 static double GetUnitsPerStopwatchCycle(EA::StdC::Stopwatch::Units);
104
105 private:
106 uint64_t mnStartTime; /// Start time; always in cycles.
107 uint64_t mnTotalElapsedTime; /// Elapsed time; always in cycles.
108 int mnUnits; /// Stopwatch units. One of enum Units or kUnitsUserDefined+.
109 float mfStopwatchCyclesToUnitsCoefficient; /// Coefficient is defined seconds per cycle (assuming you want to measure seconds). This is the inverse of the frequency, done this way for speed. Time passed = cycle count * coefficient.
110
111 }; // class Stopwatch
112
113
114
115 class EASTDC_API LimitStopwatch : public Stopwatch
116 {
117 public:
118 /// LimitStopwatch
119 /// Constructor
Stopwatch(nUnits,bStartImmediately)120 LimitStopwatch(int nUnits = kUnitsCycles, uint64_t nLimit = 0, bool bStartImmediately = false) : Stopwatch(nUnits, bStartImmediately), mnEndTime(nLimit) { }
121
122 /// IsTimeUp
123 /// Returns true if the limit has been reached. Highly efficient.
124 bool IsTimeUp() const;
125
126 protected:
127 uint64_t mnEndTime; /// The precomputed end time used by limit timing functions.
128
129 }; // class LimitStopwatch
130
131 }} // namespace EA::StdC
132
133
134 inline
IsTimeUp()135 bool EA::StdC::LimitStopwatch::IsTimeUp() const
136 {
137 const uint64_t nCurrentTime = GetStopwatchCycle();
138 return (int64_t)(mnEndTime - nCurrentTime) < 0; // We do this instead of a straight compare to deal with possible integer wraparound issues.
139 }
140
141
142 inline
Start()143 void EA::StdC::Stopwatch::Start()
144 {
145 if(!mnStartTime) // If not already started...
146 {
147 if(mnUnits == kUnitsCPUCycles)
148 mnStartTime = GetCPUCycle();
149 else
150 mnStartTime = GetStopwatchCycle();
151
152 }
153 }
154
155 inline
Stop()156 void EA::StdC::Stopwatch::Stop()
157 {
158 if(mnStartTime) // Check to make sure the stopwatch is actually running
159 {
160 const uint64_t nCurrentTime(mnUnits == kUnitsCPUCycles ? GetCPUCycle() : GetStopwatchCycle());
161 mnTotalElapsedTime += (nCurrentTime - mnStartTime);
162 mnStartTime = 0;
163 }
164 }
165
166 inline
Restart()167 void EA::StdC::Stopwatch::Restart()
168 {
169 mnStartTime = 0;
170 mnTotalElapsedTime = 0;
171 mfStopwatchCyclesToUnitsCoefficient = 1.f;
172
173 Start();
174 }
175
176 inline
GetUnits()177 int EA::StdC::Stopwatch::GetUnits()
178 {
179 return mnUnits;
180 }
181
182
183 inline
GetElapsedTime()184 uint64_t EA::StdC::Stopwatch::GetElapsedTime() const
185 {
186 uint64_t nFinalTotalElapsedTime64(mnTotalElapsedTime);
187
188 if(mnStartTime) // We we are currently running, then take into account time passed since last start.
189 {
190 uint64_t nCurrentTime;
191
192 // See the 'Stop' function for an explanation of the code below.
193 if(mnUnits == kUnitsCPUCycles)
194 nCurrentTime = GetCPUCycle();
195 else
196 nCurrentTime = GetStopwatchCycle();
197
198 uint64_t nElapsed = nCurrentTime - mnStartTime;
199 nFinalTotalElapsedTime64 += nElapsed;
200
201 } // Now nFinalTotalElapsedTime64 holds the elapsed time in stopwatch cycles.
202
203 // We are doing a float to int cast here, which is a relatively slow operation on some CPUs.
204 return (uint64_t)((nFinalTotalElapsedTime64 * mfStopwatchCyclesToUnitsCoefficient) + 0.49999f);
205 }
206
207 inline
GetElapsedTimeFloat()208 float EA::StdC::Stopwatch::GetElapsedTimeFloat() const
209 {
210 uint64_t nFinalTotalElapsedTime64(mnTotalElapsedTime);
211
212 if(mnStartTime) // We we are currently running, then take into account time passed since last start.
213 {
214 uint64_t nCurrentTime;
215
216 // See the 'Stop' function for an explanation of the code below.
217 if(mnUnits == kUnitsCPUCycles)
218 nCurrentTime = GetCPUCycle();
219 else
220 nCurrentTime = GetStopwatchCycle();
221
222 uint64_t nElapsed = nCurrentTime - mnStartTime;
223 nFinalTotalElapsedTime64 += nElapsed;
224
225 } // Now nFinalTotalElapsedTime64 holds the elapsed time in stopwatch cycles.
226
227 return (nFinalTotalElapsedTime64 * mfStopwatchCyclesToUnitsCoefficient) + 0.49999f;
228 }
229
230
231 // Other supported processors have fixed-frequency CPUs and thus can
232 // directly use the GetCPUCycle functionality for maximum precision
233 // and speed.
GetStopwatchCycle()234 inline uint64_t EA::StdC::Stopwatch::GetStopwatchCycle()
235 {
236 return GetCPUCycle();
237 }
238
239 namespace Internal
240 {
GetPlatformCycleFrequency()241 inline double GetPlatformCycleFrequency()
242 {
243 using namespace std::chrono;
244 #ifdef EA_PLATFORM_MINGW
245 using clock = steady_clock;
246 #else
247 using clock = high_resolution_clock;
248 #endif
249 double perfFreq = static_cast<double>(clock::period::num) / clock::period::den;
250 return perfFreq / 1000.0;
251 }
252 }
253
GetUnitsPerCPUCycle(EA::StdC::Stopwatch::Units units)254 inline double EA::StdC::Stopwatch::GetUnitsPerCPUCycle(EA::StdC::Stopwatch::Units units)
255 {
256 switch(units)
257 {
258 case kUnitsCycles:
259 case kUnitsCPUCycles:
260 return Internal::GetPlatformCycleFrequency();
261
262 // NOTE: These aren't used by the existing benchmarks.
263 case kUnitsNanoseconds:
264 case kUnitsMilliseconds:
265 case kUnitsMicroseconds:
266 case kUnitsSeconds:
267 case kUnitsMinutes:
268 case kUnitsUserDefined:
269 break;
270 }
271
272 return 1.0;
273 }
274
GetUnitsPerStopwatchCycle(EA::StdC::Stopwatch::Units units)275 inline double EA::StdC::Stopwatch::GetUnitsPerStopwatchCycle(EA::StdC::Stopwatch::Units units)
276 {
277 switch(units)
278 {
279 case kUnitsCycles:
280 case kUnitsCPUCycles:
281 return static_cast<double>(Internal::GetPlatformCycleFrequency());
282
283 // NOTE: These aren't used by the existing benchmarks.
284 case kUnitsNanoseconds:
285 case kUnitsMilliseconds:
286 case kUnitsMicroseconds:
287 case kUnitsSeconds:
288 case kUnitsMinutes:
289 case kUnitsUserDefined:
290 break;
291 }
292
293 return 1.0;
294 }
295
GetCPUCycle()296 inline uint64_t EA::StdC::Stopwatch::GetCPUCycle()
297 {
298 using namespace std::chrono;
299 #ifdef EA_PLATFORM_MINGW
300 using clock = steady_clock;
301 #else
302 using clock = high_resolution_clock;
303 #endif
304 return clock::now().time_since_epoch().count();
305 }
306
307
308 #endif // EASTDC_EASTOPWATCH_H
309