PerformanceTesting.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. #import "PerformanceTesting.h"
  2. #import "DDLog.h"
  3. #import "DDASLLogger.h"
  4. #import "DDTTYLogger.h"
  5. #import "DDFileLogger.h"
  6. #import "BaseNSLogging.h"
  7. #import "StaticLogging.h"
  8. #import "DynamicLogging.h"
  9. // Define the number of times each test is performed.
  10. // Due to various factors, the execution time of each test run may vary quite a bit.
  11. // Each test should be executed several times in order to arrive at a stable average.
  12. #define NUMBER_OF_RUNS 20
  13. /**
  14. * The idea behind the benchmark tests is simple:
  15. * How does the logging framework compare to basic NSLog statements?
  16. *
  17. * However, due to the complexity of the logging framework and its various configuration options,
  18. * it is more complicated than a single test. Thus the testing is broken up as follows:
  19. *
  20. * - 3 Suites, each representing a different configuration of the logging framework
  21. * - 5 Tests, run within each suite.
  22. *
  23. * The suites are described below in the configureLoggingForSuiteX methods.
  24. * The tests are described in the various logging files, such as StaticLogging or DynamicLogging.
  25. * Notice that these file are almost exactly the same.
  26. *
  27. * BaseNSLogging defines the log methods to use NSLog (the base we are comparing against).
  28. * StaticLogging uses a 'const' log level, meaning the compiler will prune log statements (in release mode).
  29. * DynamicLogging use a non-const log level, meaning each log statement will incur an integer comparison penalty.
  30. **/
  31. @implementation PerformanceTesting
  32. static NSTimeInterval base[5][3]; // [test][min,avg,max]
  33. static NSTimeInterval fmwk[3][2][5][3]; // [suite][file][test][min,avg,max]
  34. static DDFileLogger *fileLogger = nil;
  35. + (void)initialize
  36. {
  37. bzero(&base, sizeof(base));
  38. bzero(&fmwk, sizeof(fmwk));
  39. }
  40. + (DDFileLogger *)fileLogger
  41. {
  42. if (fileLogger == nil)
  43. {
  44. fileLogger = [[DDFileLogger alloc] init];
  45. fileLogger.maximumFileSize = (1024 * 1024 * 1); // 1 MB
  46. fileLogger.rollingFrequency = (60 * 60 * 24); // 24 Hours
  47. fileLogger.logFileManager.maximumNumberOfLogFiles = 4;
  48. }
  49. return fileLogger;
  50. }
  51. /**
  52. * Suite 1 - Logging to Console only.
  53. **/
  54. + (void)configureLoggingForSuite1
  55. {
  56. [DDLog removeAllLoggers];
  57. [DDLog addLogger:[DDASLLogger sharedInstance]];
  58. [DDLog addLogger:[DDTTYLogger sharedInstance]];
  59. }
  60. /**
  61. * Suite 2 - Logging to File only.
  62. *
  63. * We attempt to configure the logging so it will be forced to roll the log files during the test.
  64. * Rolling the log files requires creating and opening a new file.
  65. * This could be a performance hit, so we want our benchmark to take this into account.
  66. **/
  67. + (void)configureLoggingForSuite2
  68. {
  69. [DDLog removeAllLoggers];
  70. [DDLog addLogger:[self fileLogger]];
  71. }
  72. /**
  73. * Suite 3 - Logging to Console & File.
  74. **/
  75. + (void)configureLoggingForSuite3
  76. {
  77. [DDLog removeAllLoggers];
  78. [DDLog addLogger:[DDASLLogger sharedInstance]];
  79. [DDLog addLogger:[DDTTYLogger sharedInstance]];
  80. [DDLog addLogger:[self fileLogger]];
  81. }
  82. + (void)executeTestsWithBase:(BOOL)exeBase framework:(BOOL)exeFramework frameworkSuite:(int)suiteNum
  83. {
  84. if (!exeBase && !exeFramework) return;
  85. int sn = suiteNum - 1; // Zero-indexed for array
  86. int i, j, k;
  87. int start = exeBase ? 0 : 1;
  88. int finish = exeFramework ? 3 : 1;
  89. for (i = start; i < finish; i++)
  90. {
  91. Class class;
  92. switch (i)
  93. {
  94. case 0 : class = [BaseNSLogging class]; break;
  95. case 1 : class = [StaticLogging class]; break;
  96. default : class = [DynamicLogging class]; break;
  97. }
  98. for (j = 0; j < 5; j++)
  99. {
  100. NSTimeInterval min = DBL_MAX;
  101. NSTimeInterval max = DBL_MIN;
  102. NSTimeInterval total = 0.0;
  103. for (k = 0; k < NUMBER_OF_RUNS; k++)
  104. {
  105. @autoreleasepool {
  106. NSDate *start = [NSDate date];
  107. switch (j)
  108. {
  109. case 0 : [class performSelector:@selector(speedTest0)]; break;
  110. case 1 : [class performSelector:@selector(speedTest1)]; break;
  111. case 2 : [class performSelector:@selector(speedTest2)]; break;
  112. case 3 : [class performSelector:@selector(speedTest3)]; break;
  113. default : [class performSelector:@selector(speedTest4)]; break;
  114. }
  115. NSTimeInterval result = [start timeIntervalSinceNow] * -1.0;
  116. min = MIN(min, result);
  117. max = MAX(max, result);
  118. total += result;
  119. }
  120. [DDLog flushLog];
  121. }
  122. if (i == 0)
  123. {
  124. // Base
  125. base[j][0] = min;
  126. base[j][1] = total / (double)NUMBER_OF_RUNS;
  127. base[j][2] = max;
  128. }
  129. else
  130. {
  131. // Framework
  132. fmwk[sn][i-1][j][0] = min;
  133. fmwk[sn][i-1][j][1] = total / (double)NUMBER_OF_RUNS;
  134. fmwk[sn][i-1][j][2] = max;
  135. }
  136. }
  137. }
  138. }
  139. + (NSString *)printableResultsForSuite:(int)suiteNum
  140. {
  141. int sn = suiteNum - 1; // Zero-indexed for array
  142. NSMutableString *str = [NSMutableString stringWithCapacity:2000];
  143. [str appendFormat:@"Results are given as [min][avg][max] calculated over the course of %i runs.", NUMBER_OF_RUNS];
  144. [str appendString:@"\n\n"];
  145. [str appendString:@"Test 0:\n"];
  146. [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_0_COUNT];
  147. [str appendString:@"The log statement is above the log level threshold, and will not execute.\n"];
  148. [str appendString:@"The StaticLogging class will compile it out (in release mode).\n"];
  149. [str appendString:@"The DynamicLogging class will require a single integer comparison.\n"];
  150. [str appendString:@"\n"];
  151. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[0][0], base[0][1], base[0][2]];
  152. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][0][0], fmwk[sn][0][0][1], fmwk[sn][0][0][2]];
  153. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][0][0], fmwk[sn][1][0][1], fmwk[sn][1][0][2]];
  154. [str appendString:@"\n\n\n"];
  155. [str appendString:@"Test 1:\n"];
  156. [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_1_COUNT];
  157. [str appendString:@"The log statement is at or below the log level threshold, and will execute.\n"];
  158. [str appendString:@"The logging framework will execute the statements Asynchronously.\n"];
  159. [str appendString:@"\n"];
  160. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[1][0], base[1][1], base[1][2]];
  161. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][1][0], fmwk[sn][0][1][1], fmwk[sn][0][1][2]];
  162. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][1][0], fmwk[sn][1][1][1], fmwk[sn][1][1][2]];
  163. [str appendString:@"\n\n\n"];
  164. [str appendString:@"Test 2:\n"];
  165. [str appendFormat:@"Execute %i log statements.\n", SPEED_TEST_2_COUNT];
  166. [str appendString:@"The log statement is at or below the log level threshold, and will execute.\n"];
  167. [str appendString:@"The logging framework will execute the statements Synchronously.\n"];
  168. [str appendString:@"\n"];
  169. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[2][0], base[2][1], base[2][2]];
  170. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][2][0], fmwk[sn][0][2][1], fmwk[sn][0][2][2]];
  171. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][2][0], fmwk[sn][1][2][1], fmwk[sn][1][2][2]];
  172. [str appendString:@"\n\n\n"];
  173. [str appendString:@"Test 3:"];
  174. [str appendFormat:@"Execute %i log statements per level.\n", SPEED_TEST_3_COUNT];
  175. [str appendString:@"This is designed to mimic what might happen in a regular application.\n"];
  176. [str appendString:@"25% will be above log level threshold and will be filtered out.\n"];
  177. [str appendString:@"50% will execute Asynchronously.\n"];
  178. [str appendString:@"25% will execute Synchronously.\n"];
  179. [str appendString:@"\n"];
  180. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[3][0], base[3][1], base[3][2]];
  181. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][3][0], fmwk[sn][0][3][1], fmwk[sn][0][3][2]];
  182. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][3][0], fmwk[sn][1][3][1], fmwk[sn][1][3][2]];
  183. [str appendString:@"\n\n\n"];
  184. float total = 0.0F;
  185. total += SPEED_TEST_4_VERBOSE_COUNT;
  186. total += SPEED_TEST_4_INFO_COUNT;
  187. total += SPEED_TEST_4_WARN_COUNT;
  188. total += SPEED_TEST_4_ERROR_COUNT;
  189. float verbose = (float)SPEED_TEST_4_VERBOSE_COUNT / total * 100.0F;
  190. float info = (float)SPEED_TEST_4_INFO_COUNT / total * 100.0F;
  191. float warn = (float)SPEED_TEST_4_WARN_COUNT / total * 100.0F;
  192. float error = (float)SPEED_TEST_4_ERROR_COUNT / total * 100.0F;
  193. [str appendString:@"Test 4:\n"];
  194. [str appendString:@"Similar to test 3, designed to mimic a real application\n"];
  195. [str appendFormat:@"Execute %i log statements in total.\n", (int)total];
  196. [str appendFormat:@"%04.1f%% will be above log level threshold and will be filtered out.\n", verbose];
  197. [str appendFormat:@"%04.1f%% will execute Asynchronously.\n", (info + warn)];
  198. [str appendFormat:@"%04.1f%% will execute Synchronously.\n", error];
  199. [str appendString:@"\n"];
  200. [str appendFormat:@"BaseNSLogging :[%.4f][%.4f][%.4f]\n", base[4][0], base[4][1], base[4][2]];
  201. [str appendFormat:@"StaticLogging :[%.4f][%.4f][%.4f]\n", fmwk[sn][0][4][0], fmwk[sn][0][4][1], fmwk[sn][0][4][2]];
  202. [str appendFormat:@"DynamicLogging:[%.4f][%.4f][%.4f]\n", fmwk[sn][1][4][0], fmwk[sn][1][4][1], fmwk[sn][1][4][2]];
  203. [str appendString:@"\n\n\n"];
  204. return str;
  205. }
  206. + (NSString *)csvResults
  207. {
  208. NSMutableString *str = [NSMutableString stringWithCapacity:1000];
  209. // What are we trying to do here?
  210. //
  211. // What we ultimately want is to compare the performance of the framework against the baseline.
  212. // This means we want to see the performance of the baseline for test 1,
  213. // and then right next to it we want to see the performance of the framework with each various configuration.
  214. //
  215. // So we want it to kinda look like this for Test 1:
  216. //
  217. // Base, [min], [avg], [max]
  218. // Suite 1 - Static, [min], [avg], [max]
  219. // Suite 1 - Dynamic, [min], [avg], [max]
  220. // Suite 2 - Static, [min], [avg], [max]
  221. // Suite 2 - Dynamic, [min], [avg], [max]
  222. // Suite 3 - Static, [min], [avg], [max]
  223. // Suite 3 - Dynamic, [min], [avg], [max]
  224. //
  225. // This will import into Excel just fine.
  226. // However, I couldn't get Excel to make a decent looking graph with the data.
  227. // Perhaps I'm just not familiar enough with Excel.
  228. // But I was able to download OmniGraphSketcher,
  229. // and figure out how to create an awesome looking graph in less than 15 minutes.
  230. // And thus OmniGraphSketcher wins for me.
  231. // The only catch is that it wants to import the data with numbers instead of names.
  232. // So I need to convert the output to look like this:
  233. //
  234. // 0, [min], [avg], [max]
  235. // 1, [min], [avg], [max]
  236. // 2, [min], [avg], [max]
  237. // 3, [min], [avg], [max]
  238. // 4, [min], [avg], [max]
  239. // 5, [min], [avg], [max]
  240. // 6, [min], [avg], [max]
  241. //
  242. // I can then import the data into OmniGraphSketcher, and rename the X-axis points.
  243. // static NSTimeInterval base[5][3]; // [test][min,avg,max]
  244. //
  245. // static NSTimeInterval fmwk[3][2][5][3]; // [suite][file][test][min,avg,max]
  246. int row = 0;
  247. int suite, file, test;
  248. for (test = 0; test < 5; test++)
  249. {
  250. [str appendFormat:@"%i, %.4f, %.4f, %.4f\n", row++, base[test][0], base[test][1], base[test][2]];
  251. for (suite = 0; suite < 3; suite++)
  252. {
  253. for (file = 0; file < 2; file++)
  254. {
  255. [str appendFormat:@"%i, %.4f, %.4f, %.4f\n", row++,
  256. fmwk[suite][file][test][0], fmwk[suite][file][test][1], fmwk[suite][file][test][2]];
  257. }
  258. }
  259. row += 3;
  260. }
  261. return str;
  262. }
  263. + (void)startPerformanceTests
  264. {
  265. BOOL runBase = YES;
  266. BOOL runSuite1 = YES;
  267. BOOL runSuite2 = YES;
  268. BOOL runSuite3 = YES;
  269. if (!runBase && !runSuite1 && !runSuite2 && !runSuite3)
  270. {
  271. // Nothing to do, all suites disabled
  272. return;
  273. }
  274. NSLog(@"Preparing to start performance tests...");
  275. NSLog(@"The results will be printed nicely when all logging has completed.\n\n");
  276. [NSThread sleepForTimeInterval:3.0];
  277. if (runBase)
  278. {
  279. [self executeTestsWithBase:YES framework:NO frameworkSuite:0];
  280. }
  281. NSString *printableResults1 = nil;
  282. NSString *printableResults2 = nil;
  283. NSString *printableResults3 = nil;
  284. if (runSuite1)
  285. {
  286. [self configureLoggingForSuite1];
  287. [self executeTestsWithBase:NO framework:YES frameworkSuite:1];
  288. printableResults1 = [self printableResultsForSuite:1];
  289. NSLog(@"\n\n\n\n");
  290. }
  291. if (runSuite2)
  292. {
  293. [self configureLoggingForSuite2];
  294. [self executeTestsWithBase:NO framework:YES frameworkSuite:2];
  295. printableResults2 = [self printableResultsForSuite:2];
  296. NSLog(@"\n\n\n\n");
  297. }
  298. if (runSuite3)
  299. {
  300. [self configureLoggingForSuite3];
  301. [self executeTestsWithBase:NO framework:YES frameworkSuite:3];
  302. printableResults3 = [self printableResultsForSuite:3];
  303. NSLog(@"\n\n\n\n");
  304. }
  305. if (runSuite1)
  306. {
  307. NSLog(@"======================================================================");
  308. NSLog(@"Benchmark Suite 1:");
  309. NSLog(@"Logging framework configured to log to console only.");
  310. NSLog(@"\n\n%@", printableResults1);
  311. NSLog(@"======================================================================");
  312. }
  313. if (runSuite2)
  314. {
  315. NSLog(@"======================================================================");
  316. NSLog(@"Benchmark Suite 2:");
  317. NSLog(@"Logging framework configured to log to file only.");
  318. NSLog(@"\n\n%@", printableResults2);
  319. NSLog(@"======================================================================");
  320. }
  321. if (runSuite3)
  322. {
  323. NSLog(@"======================================================================");
  324. NSLog(@"Benchmark Suite 3:");
  325. NSLog(@"Logging framework configured to log to console & file.");
  326. NSLog(@"\n\n%@", printableResults3);
  327. NSLog(@"======================================================================");
  328. }
  329. #if TARGET_OS_IPHONE
  330. NSString *csvResultsPath = [@"~/Documents/LumberjackBenchmark.csv" stringByExpandingTildeInPath];
  331. #else
  332. NSString *csvResultsPath = [@"~/Desktop/LumberjackBenchmark.csv" stringByExpandingTildeInPath];
  333. #endif
  334. if (![[NSFileManager defaultManager] fileExistsAtPath:csvResultsPath])
  335. {
  336. [[NSFileManager defaultManager] createFileAtPath:csvResultsPath contents:nil attributes:nil];
  337. }
  338. NSFileHandle *csvResultsFile = [NSFileHandle fileHandleForWritingAtPath:csvResultsPath];
  339. NSString *csvRsults = [self csvResults];
  340. [csvResultsFile writeData:[csvRsults dataUsingEncoding:NSUTF8StringEncoding]];
  341. NSLog(@"CSV results file written to:\n%@", csvResultsPath);
  342. }
  343. @end