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