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