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