1 /* 2 * Copyright Lodovico Giaretta 2016 - . 3 * Distributed under the Boost Software License, Version 1.0. 4 * (See accompanying file LICENSE_1_0.txt or copy at 5 * http://www.boost.org/LICENSE_1_0.txt) 6 */ 7 8 module random_benchmark; 9 10 import genxml; 11 import std.experimental.statistical; 12 13 import std.experimental.xml; 14 15 import std.array; 16 import std.stdio; 17 import std.file; 18 import std.path: buildPath, exists; 19 import std.conv; 20 import core.time: Duration, nsecs; 21 22 // BENCHMARK CONFIGURATIONS: TWEAK AS NEEDED 23 24 enum BenchmarkConfig theBenchmark = { 25 components: [ 26 ComponentConfig("Memory_bound", "to!string", "iterateString"), 27 ComponentConfig("Parser_BufferedLexer_1K", "makeDumbBufferedReader!1024", "parserTest!(BufferedLexer, DumbBufferedReader)"), 28 ComponentConfig("Parser_BufferedLexer_64K", "makeDumbBufferedReader!65536", "parserTest!(BufferedLexer, DumbBufferedReader)"), 29 ComponentConfig("Parser_SliceLexer", "to!string", "parserTest!SliceLexer"), 30 ComponentConfig("Parser_ForwardLexer", "to!string", "parserTest!ForwardLexer"), 31 ComponentConfig("Parser_RangeLexer", "to!string", "parserTest!RangeLexer"), 32 ComponentConfig("Cursor_BufferedLexer_1K", "makeDumbBufferedReader!1024", "cursorTest!(BufferedLexer, DumbBufferedReader)"), 33 ComponentConfig("Cursor_BufferedLexer_64K", "makeDumbBufferedReader!65536", "cursorTest!(BufferedLexer, DumbBufferedReader)"), 34 ComponentConfig("Cursor_SliceLexer", "to!string", "cursorTest!SliceLexer"), 35 ComponentConfig("Cursor_ForwardLexer", "to!string", "cursorTest!ForwardLexer"), 36 ComponentConfig("Cursor_RangeLexer", "to!string", "cursorTest!RangeLexer"), 37 ComponentConfig("Legacy_SAX_API", "to!string", "oldSAXTest!\"std.xml\""), 38 ComponentConfig("Legacy_SAX_Emulator", "to!string","oldSAXTest!\"std.experimental.xml.legacy\""), 39 ], 40 configurations: [ 41 K100, 42 M1, 43 M10, 44 M100, 45 ], 46 filesPerConfig: 3, 47 runsPerFile: 5, 48 }; 49 50 enum GenXmlConfig M100 = { name: "M100", 51 minDepth: 6, 52 maxDepth: 14, 53 minChilds: 3, 54 maxChilds: 9, 55 minAttributeNum: 0, 56 maxAttributeNum: 5}; 57 58 enum GenXmlConfig M10 = { name: "M10", 59 minDepth: 5, 60 maxDepth: 13, 61 minChilds: 3, 62 maxChilds: 8, 63 minAttributeNum: 0, 64 maxAttributeNum: 4}; 65 66 enum GenXmlConfig M1 = { name: "M1", 67 minDepth: 5, 68 maxDepth: 12, 69 minChilds: 4, 70 maxChilds: 7, 71 minAttributeNum: 1, 72 maxAttributeNum: 4}; 73 74 enum GenXmlConfig K100 = { name: "K100", 75 minDepth: 4, 76 maxDepth: 11, 77 minChilds: 3, 78 maxChilds: 6, 79 minAttributeNum: 1, 80 maxAttributeNum: 3}; 81 82 // FUNCTIONS USED FOR TESTING 83 84 struct DumbBufferedReader 85 { 86 string content; 87 size_t chunk_size; 88 89 void popFront() @nogc 90 { 91 if (content.length > chunk_size) 92 content = content[chunk_size..$]; 93 else 94 content = []; 95 } 96 string front() const @nogc 97 { 98 if (content.length >= chunk_size) 99 return content[0..chunk_size]; 100 else 101 return content[0..$]; 102 } 103 bool empty() const @nogc 104 { 105 return !content.length; 106 } 107 } 108 109 DumbBufferedReader makeDumbBufferedReader(size_t bufferSize)(string s) 110 { 111 return DumbBufferedReader(s, bufferSize); 112 } 113 114 void parserTest(alias Lexer, T = string)(T data) 115 { 116 auto parser = Parser!(Lexer!(T, void delegate()), void delegate())(); 117 parser.setSource(data); 118 foreach(e; parser) 119 { 120 doNotOptimize(e); 121 } 122 } 123 124 void cursorTest(alias Lexer, T = string)(T data) 125 { 126 auto cursor = Cursor!(Parser!(Lexer!(T, void delegate()), void delegate()))(); 127 cursor.setSource(data); 128 inspectOneLevel(cursor); 129 } 130 void inspectOneLevel(T)(ref T cursor) 131 { 132 do 133 { 134 foreach(attr; cursor.getAttributes) 135 doNotOptimize(attr); 136 137 if (cursor.enter) 138 { 139 inspectOneLevel(cursor); 140 cursor.exit(); 141 } 142 } 143 while (cursor.next()); 144 } 145 146 void oldSAXTest(string api)(string data) 147 { 148 mixin("import " ~ api ~ ";\n"); 149 150 void recursiveParse(ElementParser parser) 151 { 152 doNotOptimize(cast(Tag)(parser.tag)); 153 parser.onStartTag[null] = &recursiveParse; 154 parser.parse; 155 } 156 157 DocumentParser parser = new DocumentParser(data); 158 recursiveParse(parser); 159 } 160 161 void iterateString(string data) 162 { 163 foreach (i; 0..data.length) 164 doNotOptimize(data[i]); 165 } 166 167 void doNotOptimize(T)(auto ref T result) 168 { 169 import std.process: thisProcessID; 170 if (thisProcessID == 1) 171 writeln(result); 172 } 173 174 // MAIN TEST DRIVER 175 void main(string[] args) 176 { 177 void delegate(FileStats[string], ComponentResults[string]) printFunction; 178 if (args.length > 1) 179 { 180 switch(args[1]) 181 { 182 case "csv": 183 printFunction = (stats, results) { printResultsCSV(theBenchmark, results); }; 184 break; 185 case "pretty": 186 printFunction = (stats, results) 187 { 188 printResultsByConfiguration(theBenchmark, stats, results); 189 printResultsSpeedSummary(theBenchmark, results); 190 writeln("\n\n If you are watching this on a terminal, you are encouraged to redirect the standard output to a file instead.\n"); 191 }; 192 break; 193 default: 194 stderr.writeln("Error: output format ", args[1], "is not supported!"); 195 return; 196 } 197 } 198 else 199 { 200 printFunction = (stats, results) 201 { 202 printResultsByConfiguration(theBenchmark, stats, results); 203 printResultsSpeedSummary(theBenchmark, results); 204 writeln("\n\n If you are watching this on a terminal, you are encouraged to redirect the standard output to a file instead.\n"); 205 }; 206 } 207 stderr.writeln("Generating test files..."); 208 auto stats = generateTestFiles(theBenchmark); 209 stderr.writeln("\nPerforming tests..."); 210 auto results = performBenchmark!theBenchmark; 211 stderr.writeln(); 212 printFunction(stats, results); 213 } 214 215 // STRUCTURES HOLDING PARAMETERS AND RESULTS 216 217 struct BenchmarkConfig 218 { 219 uint runsPerFile; 220 uint filesPerConfig; 221 ComponentConfig[] components; 222 GenXmlConfig[] configurations; 223 } 224 225 struct ComponentConfig 226 { 227 string name; 228 string inputFunction; 229 string benchmarkFunction; 230 } 231 232 struct ComponentResults 233 { 234 PreciseStatisticData!double speedStat; 235 ConfigResults[string] configResults; 236 } 237 238 struct ConfigResults 239 { 240 PreciseStatisticData!double speedStat; 241 FileResults[string] fileResults; 242 } 243 244 struct FileResults 245 { 246 PreciseStatisticData!(((long x) => nsecs(x)), (Duration d) => d.total!"nsecs") timeStat; 247 PreciseStatisticData!double speedStat; 248 Duration[] times; 249 double[] speeds; 250 } 251 252 // CODE FOR TESTING 253 254 mixin template BenchmarkFunctions(ComponentConfig[] comps, size_t pos = 0) 255 { 256 mixin("auto " ~ comps[pos].name ~ "_BenchmarkFunction(string data) {" 257 "import core.time: MonoTime;" 258 "auto input = " ~ comps[pos].inputFunction ~ "(data);" 259 "MonoTime before = MonoTime.currTime;" 260 ~ comps[pos].benchmarkFunction ~ "(input);" 261 "MonoTime after = MonoTime.currTime;" 262 "return after - before;" 263 "}" 264 ); 265 266 static if (pos + 1 < comps.length) 267 mixin BenchmarkFunctions!(comps, pos + 1); 268 } 269 270 auto performBenchmark(BenchmarkConfig benchmark)() 271 { 272 import std.meta; 273 274 enum ComponentConfig[] theComponents = theBenchmark.components; 275 mixin BenchmarkFunctions!(theComponents); 276 277 total_tests = benchmark.runsPerFile * benchmark.filesPerConfig * benchmark.configurations.length * benchmark.components.length; 278 ComponentResults[string] results; 279 foreach(component; aliasSeqOf!(theComponents)) 280 results[component.name] = testComponent(benchmark, mixin("&" ~ component.name ~ "_BenchmarkFunction")); 281 282 return results; 283 } 284 285 auto testComponent(BenchmarkConfig benchmark, Duration delegate(string) fun) 286 { 287 import std.algorithm: map, joiner; 288 289 ComponentResults results; 290 foreach (config; benchmark.configurations) 291 results.configResults[config.name] = testConfiguration(config.name, benchmark.filesPerConfig, benchmark.runsPerFile, fun); 292 293 results.speedStat = PreciseStatisticData!double(results.configResults.byValue.map!"a.fileResults.byValue".joiner.map!"a.speeds".joiner); 294 return results; 295 } 296 297 auto testConfiguration(string config, uint files, uint runs, Duration delegate(string) fun) 298 { 299 import std.algorithm: map, joiner; 300 301 ConfigResults results; 302 foreach (filename; getConfigFiles(config, files)) 303 results.fileResults[filename] = testFile(filename, runs, fun); 304 305 results.speedStat = PreciseStatisticData!double(results.fileResults.byValue.map!"a.speeds".joiner); 306 return results; 307 } 308 309 ulong total_tests; 310 ulong performed_tests; 311 auto testFile(string name, uint runs, Duration delegate(string) fun) 312 { 313 FileResults results; 314 auto input = readText(name); 315 foreach (run; 0..runs) 316 { 317 auto time = fun(input); 318 results.times ~= time; 319 results.speeds ~= (cast(double)getSize(name)) / time.total!"usecs"; 320 stderr.writef("\r%d out of %d tests performed", ++performed_tests, total_tests); 321 } 322 results.timeStat = typeof(results.timeStat)(results.times); 323 results.speedStat = PreciseStatisticData!double(results.speeds); 324 return results; 325 } 326 327 string[] getConfigFiles(string config, uint maxFiles) 328 { 329 string[] results = []; 330 int count = 1; 331 while(count <= maxFiles && buildPath("random-benchmark", config ~ "_" ~ to!string(count) ~ ".xml").exists) 332 { 333 results ~= buildPath("random-benchmark", config ~ "_" ~ to!string(count) ~ ".xml"); 334 count++; 335 } 336 return results; 337 } 338 339 auto generateTestFiles(BenchmarkConfig benchmark) 340 { 341 if(!exists("random-benchmark")) 342 mkdir("random-benchmark"); 343 344 FileStats[string] results; 345 346 total_files = benchmark.filesPerConfig * benchmark.configurations.length; 347 foreach (config; benchmark.configurations) 348 results.merge!"a"(generateTestFiles(config, config.name, benchmark.filesPerConfig)); 349 350 return results; 351 } 352 353 ulong total_files; 354 ulong generated_files; 355 auto generateTestFiles(GenXmlConfig config, string name, int count) 356 { 357 FileStats[string] results; 358 359 foreach (i; 0..count) 360 { 361 auto filename = buildPath("random-benchmark", name ~ "_" ~ to!string(i+1) ~".xml" ); 362 if(!filename.exists) 363 { 364 auto file = File(filename, "w"); 365 results[filename] = genDocument(file.lockingTextWriter, config); 366 } 367 else 368 results[filename] = FileStats.init; 369 stderr.writef("\r%d out of %d files generated", ++generated_files, total_files); 370 } 371 return results; 372 } 373 374 void merge(alias f, V, K)(ref V[K] first, const V[K] second) 375 { 376 import std.functional: binaryFun; 377 alias fun = binaryFun!f; 378 379 foreach (kv; second.byKeyValue) 380 if (kv.key in first) 381 first[kv.key] = fun(first[kv.key], kv.value); 382 else 383 first[kv.key] = kv.value; 384 } 385 386 // CODE FOR PRETTY PRINTING 387 388 string center(string str, ulong totalSpace) 389 { 390 Appender!string result; 391 auto whiteSpace = totalSpace - str.length; 392 ulong before = whiteSpace / 2; 393 foreach (i; 0..before) 394 result.put(' '); 395 result.put(str); 396 foreach (i; before..whiteSpace) 397 result.put(' '); 398 return result.data; 399 } 400 401 Duration round(string unit)(Duration d) 402 { 403 import core.time: dur; 404 return dur!unit(d.total!unit); 405 } 406 407 void printTimesForConfiguration(BenchmarkConfig benchmark, ComponentResults[string] results, string config) 408 { 409 import std.algorithm: max, map, maxCount; 410 import std.range: repeat, take; 411 auto component_width = max(results.byKey.map!"a.length".maxCount[0], 8); 412 auto std_width = 16; 413 414 string formatDuration(Duration d) 415 { 416 import std.format: format; 417 auto millis = d.total!"msecs"; 418 if (millis < 1000) 419 return format("%3u ms", millis); 420 else if (millis < 10000) 421 return format("%.2f s", millis/1000.0); 422 else if (millis < 100000) 423 return format("%.1f s", millis/1000.0); 424 else 425 return to!string(millis/1000) ~ " s"; 426 } 427 428 foreach (component; benchmark.components) 429 { 430 write("\r " , component.name, ":", repeat(' ').take(component_width - component.name.length)); 431 foreach (file; config.getConfigFiles(benchmark.filesPerConfig)) 432 { 433 auto res = results[component.name].configResults[config].fileResults[file].timeStat; 434 write(center("min: " ~ formatDuration(res.min), std_width)); 435 write(center("avg: " ~ formatDuration(res.mean), std_width)); 436 write(center("max: " ~ formatDuration(res.max), std_width)); 437 write(center("median: " ~ formatDuration(res.median), std_width + 3)); 438 writeln(center("deviation: " ~ formatDuration(res.deviation), std_width + 6)); 439 write(repeat(' ').take(component_width+3)); 440 } 441 } 442 writeln(); 443 } 444 445 void printSpeedsForConfiguration(BenchmarkConfig benchmark, ComponentResults[string] results, string config) 446 { 447 import std.range: repeat, take; 448 import std.algorithm: max, map, maxCount; 449 import std.format: format; 450 451 auto component_width = max(results.byKey.map!"a.length".maxCount[0], 13); 452 auto speed_column_width = 8UL; 453 auto large_column_width = 12; 454 auto spaces = repeat(' '); 455 auto lines = repeat('-'); 456 auto boldLines = repeat('='); 457 write(spaces.take(component_width)); 458 foreach (i; 0..benchmark.filesPerConfig) 459 { 460 write("|"); 461 write(center("file " ~ to!string(i+1), 3*speed_column_width + 2)); 462 } 463 writeln(); 464 write(spaces.take(component_width)); 465 foreach (i; 0..benchmark.filesPerConfig) 466 { 467 write("|"); 468 write(lines.take(3*speed_column_width + 2)); 469 } 470 writeln(); 471 write(center("Speeds (MB/s)", component_width)); 472 foreach (i; 0..benchmark.filesPerConfig) 473 { 474 write("|"); 475 write(center("min", speed_column_width)); 476 write("|"); 477 write(center("avg", speed_column_width)); 478 write("|"); 479 write(center("max", speed_column_width)); 480 } 481 writeln(); 482 write(spaces.take(component_width)); 483 foreach (i; 0..benchmark.filesPerConfig) 484 { 485 write("|"); 486 write(lines.take(3*speed_column_width + 2)); 487 } 488 writeln(); 489 write(spaces.take(component_width)); 490 foreach (i; 0..benchmark.filesPerConfig) 491 { 492 write("|"); 493 write(center("median", large_column_width)); 494 write("|"); 495 write(center("deviation", large_column_width + 1)); 496 } 497 writeln(); 498 foreach (component; benchmark.components) 499 { 500 writeln(boldLines.take(component_width + benchmark.filesPerConfig*(3 + 3*speed_column_width))); 501 write(spaces.take(component_width)); 502 foreach (file; getConfigFiles(config, benchmark.filesPerConfig)) 503 { 504 auto fres = results[component.name].configResults[config].fileResults[file]; 505 write("|"); 506 write(center(format("%6.2f", fres.speedStat.min), speed_column_width)); 507 write("|"); 508 write(center(format("%6.2f", fres.speedStat.mean), speed_column_width)); 509 write("|"); 510 write(center(format("%6.2f", fres.speedStat.max), speed_column_width)); 511 } 512 writeln(); 513 write(center(component.name, component_width)); 514 foreach (i; 0..benchmark.filesPerConfig) 515 { 516 write("|"); 517 write(lines.take(3*speed_column_width + 2)); 518 } 519 writeln(); 520 write(spaces.take(component_width)); 521 foreach (file; getConfigFiles(config, benchmark.filesPerConfig)) 522 { 523 auto fres = results[component.name].configResults[config].fileResults[file]; 524 write("|"); 525 write(center(format("%6.2f", fres.speedStat.median), large_column_width)); 526 write("|"); 527 write(center(format("%6.2f", fres.speedStat.deviation), large_column_width + 1)); 528 } 529 writeln(); 530 } 531 } 532 533 void printFilesForConfiguration(string config, uint maxFiles, FileStats[string] filestats) 534 { 535 import std.algorithm: each, map, max, maxCount; 536 import std.path: pathSplitter, stripExtension; 537 import std.file: getSize; 538 539 ulong columnWidth = 16; 540 541 void writeSized(ulong value, string measure = " ") 542 { 543 import std.format: format; 544 import std.range: repeat, take; 545 import std.math: ceil, log10; 546 547 string formatted; 548 if (!value) 549 { 550 formatted = " 0 "; 551 } 552 else 553 { 554 static immutable string[] order = [" ", " k", " M", " G", " T"]; 555 auto lg = cast(int)ceil(log10(value)); 556 auto dec = (lg%3 == 0)? 0 : 3 - lg%3; 557 auto ord = (lg-1)/3; 558 auto val = value/(1000.0^^ord); 559 auto fmt = (dec?"":" ") ~ "%." ~ to!string(dec) ~ "f"; 560 auto f = format(fmt, val); 561 formatted = f ~ order[ord]; 562 } 563 formatted ~= measure; 564 formatted.center(columnWidth).write; 565 } 566 void writeAttribute(string attr, string measure = " ")() 567 { 568 foreach(name; config.getConfigFiles(maxFiles)) 569 { 570 if (filestats[name] != FileStats.init) 571 mixin("writeSized(filestats[name]." ~ attr ~ ", \"" ~ measure ~ "\");"); 572 else 573 center("-", columnWidth).write; 574 } 575 } 576 577 write("\n names: "); 578 foreach(name; config.getConfigFiles(maxFiles)) 579 name.pathSplitter.back.stripExtension.center(columnWidth).write; 580 581 write("\n total file size: "); 582 foreach(name; config.getConfigFiles(maxFiles)) 583 writeSized(name.getSize(), "B"); 584 585 write("\n raw text content: "); 586 writeAttribute!("textChars", "B"); 587 588 write("\n useless spacing: "); 589 writeAttribute!("spaces", "B"); 590 591 write("\n total nodes: "); 592 foreach(name; config.getConfigFiles(maxFiles)) 593 if (filestats[name] != FileStats.init) 594 { 595 auto stat = filestats[name]; 596 auto total = stat.elements + stat.textNodes + stat.cdataNodes + stat.processingInstructions + stat.comments + stat.attributes; 597 writeSized(total); 598 } 599 else 600 center("-", columnWidth).write; 601 602 write("\n element nodes: "); 603 writeAttribute!("elements"); 604 605 write("\n attribute nodes: "); 606 writeAttribute!("attributes", "B"); 607 608 write("\n text nodes: "); 609 writeAttribute!("textNodes", "B"); 610 611 write("\n cdata nodes: "); 612 writeAttribute!("cdataNodes", "B"); 613 614 write("\n comment nodes: "); 615 writeAttribute!("comments", "B"); 616 writeln(); 617 } 618 619 void printResultsByConfiguration(BenchmarkConfig benchmark, FileStats[string] filestats, ComponentResults[string] results) 620 { 621 uint i = 1; 622 foreach (config; benchmark.configurations) 623 { 624 writeln("\n=== CONFIGURATION " ~ to!string(i) ~ ": " ~ config.name ~ " ===\n"); 625 writeln("Timings:\n"); 626 printTimesForConfiguration(benchmark, results, config.name); 627 printSpeedsForConfiguration(benchmark, results, config.name); 628 writeln("\nConfiguration Parameters:"); 629 config.prettyPrint(2).write; 630 writeln("\nFile Statistics (only for newly created files):"); 631 printFilesForConfiguration(config.name, benchmark.filesPerConfig, filestats); 632 i++; 633 } 634 } 635 636 void printResultsSpeedSummary(BenchmarkConfig benchmark, ComponentResults[string] results) 637 { 638 import std.range: repeat, take; 639 import std.algorithm: max, map, maxCount; 640 import std.format: format; 641 642 auto component_width = max(results.byKey.map!"a.length".maxCount[0], 13); 643 auto speed_column_width = 8UL; 644 auto large_column_width = 12; 645 auto spaces = repeat(' '); 646 auto lines = repeat('-'); 647 auto boldLines = repeat('='); 648 649 writeln("\n=== CONFIGURATIONS SUMMARY ===\n"); 650 651 write(spaces.take(component_width)); 652 foreach (i; 0..benchmark.configurations.length) 653 { 654 write("|"); 655 write(center("config " ~ to!string(i+1), 3*speed_column_width + 2)); 656 } 657 writeln(); 658 write(spaces.take(component_width)); 659 foreach (i; 0..benchmark.configurations.length) 660 { 661 write("|"); 662 write(lines.take(3*speed_column_width + 2)); 663 } 664 writeln(); 665 write(center("Speeds (MB/s)", component_width)); 666 foreach (i; 0..benchmark.configurations.length) 667 { 668 write("|"); 669 write(center("min", speed_column_width)); 670 write("|"); 671 write(center("avg", speed_column_width)); 672 write("|"); 673 write(center("max", speed_column_width)); 674 } 675 writeln(); 676 write(spaces.take(component_width)); 677 foreach (i; 0..benchmark.configurations.length) 678 { 679 write("|"); 680 write(lines.take(3*speed_column_width + 2)); 681 } 682 writeln(); 683 write(spaces.take(component_width)); 684 foreach (i; 0..benchmark.configurations.length) 685 { 686 write("|"); 687 write(center("median", large_column_width)); 688 write("|"); 689 write(center("deviation", large_column_width + 1)); 690 } 691 writeln(); 692 foreach (component; benchmark.components) 693 { 694 writeln(boldLines.take(component_width + benchmark.configurations.length*(3 + 3*speed_column_width))); 695 write(spaces.take(component_width)); 696 foreach (config; benchmark.configurations) 697 { 698 auto fres = results[component.name].configResults[config.name]; 699 write("|"); 700 write(center(format("%6.2f", fres.speedStat.min), speed_column_width)); 701 write("|"); 702 write(center(format("%6.2f", fres.speedStat.mean), speed_column_width)); 703 write("|"); 704 write(center(format("%6.2f", fres.speedStat.max), speed_column_width)); 705 } 706 writeln(); 707 write(center(component.name, component_width)); 708 foreach (i; 0..benchmark.configurations.length) 709 { 710 write("|"); 711 write(lines.take(3*speed_column_width + 2)); 712 } 713 writeln(); 714 write(spaces.take(component_width)); 715 foreach (config; benchmark.configurations) 716 { 717 auto fres = results[component.name].configResults[config.name]; 718 write("|"); 719 write(center(format("%6.2f", fres.speedStat.median), large_column_width)); 720 write("|"); 721 write(center(format("%6.2f", fres.speedStat.deviation), large_column_width + 1)); 722 } 723 writeln(); 724 } 725 } 726 727 void printResultsCSV(BenchmarkConfig benchmark, ComponentResults[string] results) 728 { 729 import std.datetime: Clock; 730 import core.time: ClockType; 731 auto now = Clock.currTime!(ClockType.second); 732 auto timeStr = now.toISOExtString; 733 734 import std.format: format; 735 auto fmtP = "P, " ~ timeStr ~ ", %s, %f, %f, %f, %f, %f"; 736 auto fmtC = "C, " ~ timeStr ~ ", %s, %s, %f, %f, %f, %f, %f"; 737 auto fmtF = "F, " ~ timeStr ~ ", %s, %s, %s, %f, %f, %f, %f, %f"; 738 739 foreach (component; benchmark.components) 740 { 741 auto compStats = results[component.name].speedStat; 742 fmtP.format(component.name, compStats.min, compStats.mean, compStats.max, compStats.median, compStats.deviation).writeln; 743 744 foreach (config; benchmark.configurations) 745 { 746 auto confStats = results[component.name].configResults[config.name].speedStat; 747 fmtC.format(component.name, config.name, confStats.min, confStats.mean, confStats.max, confStats.median, confStats.deviation).writeln; 748 749 foreach (file, fileResults; results[component.name].configResults[config.name].fileResults) 750 { 751 auto fileStats = fileResults.speedStat; 752 fmtF.format(component.name, config.name, file, fileStats.min, fileStats.mean, fileStats.max, fileStats.median, fileStats.deviation).writeln; 753 } 754 } 755 } 756 }