1 /////////////////////////////////////////////////////////////////////////////
2 // Copyright (c) Electronic Arts Inc. All rights reserved.
3 /////////////////////////////////////////////////////////////////////////////
4 
5 
6 #include "EASTLBenchmark.h"
7 #include "EASTLTest.h"
8 #include <EASTL/string.h>
9 #include <EAMain/EAMain.h>
10 
11 #ifdef _MSC_VER
12 	#pragma warning(push, 0)
13 #endif
14 #include <stdio.h>
15 #include <math.h>
16 #include <float.h>
17 #ifdef _MSC_VER
18 	#pragma warning(pop)
19 #endif
20 
21 
22 
23 namespace Benchmark
24 {
ConvertStopwatchUnits(EA::StdC::Stopwatch::Units unitsSource,int64_t valueSource,EA::StdC::Stopwatch::Units unitsDest)25 	static int64_t ConvertStopwatchUnits(EA::StdC::Stopwatch::Units unitsSource, int64_t valueSource, EA::StdC::Stopwatch::Units unitsDest)
26 	{
27 		using namespace EA::StdC;
28 
29 		int64_t valueDest = valueSource;
30 
31 		if(unitsSource != unitsDest)
32 		{
33 			double sourceMultiplier;
34 
35 			switch (unitsSource)
36 			{
37 				case Stopwatch::kUnitsCPUCycles:
38 					sourceMultiplier = Stopwatch::GetUnitsPerCPUCycle(unitsDest); // This will typically be a number less than 1.
39 					valueDest = (int64_t)(valueSource * sourceMultiplier);
40 					break;
41 
42 				case Stopwatch::kUnitsCycles:
43 					sourceMultiplier = Stopwatch::GetUnitsPerStopwatchCycle(unitsDest); // This will typically be a number less than 1.
44 					valueDest = (int64_t)(valueSource * sourceMultiplier);
45 					break;
46 
47 				case Stopwatch::kUnitsNanoseconds:
48 				case Stopwatch::kUnitsMicroseconds:
49 				case Stopwatch::kUnitsMilliseconds:
50 				case Stopwatch::kUnitsSeconds:
51 				case Stopwatch::kUnitsMinutes:
52 				case Stopwatch::kUnitsUserDefined:
53 					// To do. Also, handle the case of unitsDest being Cycles or CPUCycles and unitsSource being a time.
54 					break;
55 			}
56 		}
57 
58 		return valueDest;
59 	}
60 
WriteTime(int64_t timeNS,eastl::string & sTime)61 	void WriteTime(int64_t timeNS, eastl::string& sTime)
62 	{
63 		if(timeNS > 1000000000)
64 			sTime.sprintf(" %6.2f s",  (double)timeNS / 1000000000);
65 		else if(timeNS > 1000000)
66 			sTime.sprintf("%6.1f ms", (double)timeNS / 1000000);
67 		else if(timeNS > 1000)
68 			sTime.sprintf("%6.1f us", (double)timeNS / 1000);
69 		else
70 			sTime.sprintf("%6.1f ns", (double)timeNS / 1);
71 	}
72 
73 
74 
75 	Environment gEnvironment;
76 
GetEnvironment()77 	Environment& GetEnvironment()
78 	{
79 		return gEnvironment;
80 	}
81 
82 
83 
84 	ResultSet gResultSet;
85 
GetResultSet()86 	ResultSet& GetResultSet()
87 	{
88 		return gResultSet;
89 	}
90 
91 
92 
93 	// Scratch sprintf buffer
94 	char gScratchBuffer[1024];
95 
96 
DoNothing(...)97 	void DoNothing(...)
98 	{
99 		// Intentionally nothing.
100 	}
101 
102 
AddResult(const char * pName,int units,int64_t nTime1,int64_t nTime2,const char * pNotes)103 	void AddResult(const char* pName, int units, int64_t nTime1, int64_t nTime2, const char* pNotes)
104 	{
105 		Result result;
106 
107 		result.msName   = pName;
108 		result.mUnits   = units;
109 		result.mTime1   = nTime1;
110 		result.mTime1NS = ConvertStopwatchUnits((EA::StdC::Stopwatch::Units)units, nTime1, EA::StdC::Stopwatch::kUnitsNanoseconds);
111 		result.mTime2   = nTime2;
112 		result.mTime2NS = ConvertStopwatchUnits((EA::StdC::Stopwatch::Units)units, nTime2, EA::StdC::Stopwatch::kUnitsNanoseconds);
113 
114 		if(pNotes)
115 			result.msNotes = pNotes;
116 
117 		gResultSet.insert(result);
118 	}
119 
120 
PrintResultLine(const Result & result)121 	void PrintResultLine(const Result& result)
122 	{
123 		const double fRatio         = (double)result.mTime1 / (double)result.mTime2;
124 		const double fRatioPrinted  = (fRatio > 100) ? 100 : fRatio;
125 		const double fPercentChange = fabs(((double)result.mTime1 - (double)result.mTime2) / (((double)result.mTime1 + (double)result.mTime2) / 2));
126 		const bool   bDifference    = (result.mTime1 > 10) && (result.mTime2 > 10) && (fPercentChange > 0.25);
127 		const char*  pDifference    = (bDifference ? (result.mTime1 < result.mTime2 ? "-" : "+") : "");
128 
129 		eastl::string sClockTime1, sClockTime2;
130 
131 		WriteTime(result.mTime1NS, sClockTime1);  // This converts an integer in nanoseconds (e.g. 23400000) to a string (e.g. "23.4 ms")
132 		WriteTime(result.mTime2NS, sClockTime2);
133 
134 		EA::UnitTest::Report("%-43s | %13" PRIu64 " %s | %13" PRIu64 " %s | %10.2f%10s", result.msName.c_str(), result.mTime1, sClockTime1.c_str(), result.mTime2, sClockTime2.c_str(), fRatioPrinted, pDifference);
135 
136 		if(result.msNotes.length()) // If there are any notes...
137 			EA::UnitTest::Report("   %s", result.msNotes.c_str());
138 		EA::UnitTest::Report("\n");
139 	}
140 
141 
142 	#if defined(EASTL_BENCHMARK_WRITE_FILE) && EASTL_BENCHMARK_WRITE_FILE
143 
144 		#if !defined(EASTL_BENCHMARK_WRITE_FILE_PATH)
145 			#define EASTL_BENCHMARK_WRITE_FILE_PATH "BenchmarkResults.txt"
146 		#endif
147 
148 		struct FileWriter
149 		{
150 			FILE* mpReportFile;
151 			EA::EAMain::ReportFunction mpSavedReportFunction;
152 			static FileWriter* gpFileWriter;
153 
StaticPrintfReportFunctionBenchmark::FileWriter154 			static void StaticPrintfReportFunction(const char8_t* pText)
155 			{
156 				if(gpFileWriter)
157 					gpFileWriter->PrintfReportFunction(pText);
158 			}
159 
PrintfReportFunctionBenchmark::FileWriter160 			void PrintfReportFunction(const char8_t* pText)
161 			{
162 				fwrite(pText, strlen(pText), 1, mpReportFile);
163 				EA::EAMain::ReportFunction gpReportFunction = EA::EAMain::GetDefaultReportFunction();
164 				gpReportFunction(pText);
165 			}
166 
FileWriterBenchmark::FileWriter167 			FileWriter() : mpReportFile(NULL), mpSavedReportFunction(NULL)
168 			{
169 				mpReportFile = fopen(EASTL_BENCHMARK_WRITE_FILE_PATH, "w+");
170 
171 				if(mpReportFile)
172 				{
173 					gpFileWriter = this;
174 					mpSavedReportFunction = EA::EAMain::GetDefaultReportFunction();
175 					EA::EAMain::SetReportFunction(StaticPrintfReportFunction);
176 				}
177 			}
178 
~FileWriterBenchmark::FileWriter179 		   ~FileWriter()
180 			{
181 				if(mpReportFile)
182 				{
183 					gpFileWriter = NULL;
184 					EA::EAMain::SetReportFunction(mpSavedReportFunction);
185 					fclose(mpReportFile);
186 				}
187 			}
188 		};
189 
190 		FileWriter* FileWriter::gpFileWriter = NULL;
191 	#endif
192 
193 
PrintResults()194 	void PrintResults()
195 	{
196 		#if defined(EASTL_BENCHMARK_WRITE_FILE) && EASTL_BENCHMARK_WRITE_FILE
197 			FileWriter fileWriter; // This will auto-execute.
198 		#endif
199 
200 		// Print the results
201 		EA::UnitTest::Report("\n");
202 		EA::UnitTest::Report("****************************************************************************************\n");
203 		EA::UnitTest::Report("EASTL Benchmark test results\n");
204 		EA::UnitTest::Report("****************************************************************************************\n");
205 		EA::UnitTest::Report("\n");
206 		EA::UnitTest::Report("EASTL version: %s\n", EASTL_VERSION);
207 		EA::UnitTest::Report("Platform: %s\n", gEnvironment.msPlatform.c_str());
208 		EA::UnitTest::Report("Compiler: %s\n", EA_COMPILER_STRING);
209 		#if defined(EA_DEBUG) || defined(_DEBUG)
210 		EA::UnitTest::Report("Allocator: PPMalloc::GeneralAllocatorDebug. Thread safety enabled.\n");
211 		EA::UnitTest::Report("Build: Debug. Inlining disabled. STL debug features disabled.\n");
212 		#else
213 		EA::UnitTest::Report("Allocator: PPMalloc::GeneralAllocator. Thread safety enabled.\n");
214 		EA::UnitTest::Report("Build: Full optimization. Inlining enabled.\n");
215 		#endif
216 		EA::UnitTest::Report("\n");
217 		EA::UnitTest::Report("Values are ticks and time to complete tests; smaller values are better.\n");
218 		EA::UnitTest::Report("\n");
219 		EA::UnitTest::Report("%-43s%26s%26s%13s%13s\n", "Test", gEnvironment.msSTLName1.c_str(), gEnvironment.msSTLName2.c_str(), "Ratio", "Difference?");
220 		EA::UnitTest::Report("---------------------------------------------------------------------------------------------------------------------\n");
221 
222 		eastl::string sTestTypeLast;
223 		eastl::string sTestTypeTemp;
224 
225 		for(ResultSet::iterator it = gResultSet.begin(); it != gResultSet.end(); ++it)
226 		{
227 			const Result& result = *it;
228 
229 			eastl_size_t n = result.msName.find('/');
230 			if(n == eastl::string::npos)
231 				n = result.msName.length();
232 			sTestTypeTemp.assign(result.msName, 0, n);
233 
234 			if(sTestTypeTemp != sTestTypeLast) // If it looks like we are changing to a new test type... add an empty line to help readability.
235 			{
236 				if(it != gResultSet.begin())
237 					EA::UnitTest::Report("\n");
238 				sTestTypeLast = sTestTypeTemp;
239 			}
240 
241 			PrintResultLine(result);
242 		}
243 
244 		// We will print out a final line that has the sum of the rows printed above.
245 		Result resultSum;
246 		resultSum.msName = "sum";
247 
248 		for(ResultSet::iterator its = gResultSet.begin(); its != gResultSet.end(); ++its)
249 		{
250 			const Result& resultTemp = *its;
251 
252 			EASTL_ASSERT(resultTemp.mUnits == EA::StdC::Stopwatch::kUnitsCPUCycles); // Our ConvertStopwatchUnits call below assumes that every measured time is CPUCycles.
253 			resultSum.mTime1 += resultTemp.mTime1;
254 			resultSum.mTime2 += resultTemp.mTime2;
255 		}
256 
257 		// We do this convert as a final step instead of the loop in order to avoid loss of precision.
258 		resultSum.mTime1NS = ConvertStopwatchUnits(EA::StdC::Stopwatch::kUnitsCPUCycles, resultSum.mTime1, EA::StdC::Stopwatch::kUnitsNanoseconds);
259 		resultSum.mTime2NS = ConvertStopwatchUnits(EA::StdC::Stopwatch::kUnitsCPUCycles, resultSum.mTime2, EA::StdC::Stopwatch::kUnitsNanoseconds);
260 		EA::UnitTest::Report("\n");
261 		PrintResultLine(resultSum);
262 
263 		EA::UnitTest::Report("\n");
264 		EA::UnitTest::Report("****************************************************************************************\n");
265 		EA::UnitTest::Report("\n");
266 
267 		// Clear the results
268 		gResultSet.clear();
269 		gEnvironment.clear();
270 	}
271 
272 } // namespace Benchmark
273 
274 
275 
276 
277 
278 
279 
280 
281 
282 
283 
284 
285 
286 
287 
288 
289 
290 
291 
292