PerformanceTesting.m 14 KB

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