1 /// A minimal benchmarking harness.
2 module chunker.internal.benchmark;
3 
4 /**
5  * To make a module benchmarkable, add:
6  * ---
7  * version (benchmarkYourModule) { import chunker.internal.benchmark; mixin BenchmarkThisModule; }
8  * ---
9  *
10  * Then, declare benchmarks by declaring private functions with a name
11  * starting with "benchmark", e.g. `void benchmarkFoo() { ... }`.
12  *
13  * To benchmark a benchmarkable module, run:
14  * ---
15  * dmd -version=benchmarkYourModule -run module.d [--N=<iterations>] [BenchmarkName ...]
16  * ---
17  */
18 mixin template BenchmarkThisModule()
19 {
20 	void main()
21 	{
22 		Benchmark.run();
23 	}
24 
25 	struct Benchmark
26 	{
27 	static:
28 		immutable ulong iterations;
29 		private immutable string[] benchmarks;
30 
31 		shared static this()
32 		{
33 			import core.runtime : Runtime;
34 			import std.getopt : getopt;
35 			ulong n = 1;
36 			auto args = Runtime.args;
37 			getopt(args,
38 				"N", &n,
39 			);
40 			Benchmark.iterations = n;
41 
42 			auto benchmarks = args[1..$].idup;
43 			if (!benchmarks.length)
44 				benchmarks = ["*"];
45 			Benchmark.benchmarks = benchmarks;
46 		}
47 
48 		private void run()
49 		{
50 			import std.stdio : stderr;
51 			import std.path : globMatch;
52 
53 			alias mod = __traits(parent, main);
54 			foreach (name; __traits(allMembers, mod))
55 				static if (is(typeof(__traits(getMember, mod, name))))
56 				{
57 					alias member = __traits(getMember, mod, name);
58 					static if (__traits(isStaticFunction, member))
59 					{
60 						static if (name.length > 9 && name[0..9] == "benchmark")
61 						{
62 							bool found = false;
63 							foreach (mask; benchmarks)
64 								if (globMatch(name[9..$], mask))
65 								{
66 									found = true;
67 									break;
68 								}
69 							if (!found)
70 								continue;
71 
72 							stderr.writefln("Running benchmark %s.%s (%d iterations):",
73 								__traits(identifier, mod), name[9..$], iterations);
74 							best = Duration.max;
75 							benchmarked = false;
76 							member();
77 							assert(benchmarked, "No block was benchmarked during this benchmark.");
78 							stderr.writefln("  -> %s", best);
79 						}
80 					}
81 				}
82 		}
83 
84 		import std.datetime.stopwatch : StopWatch;
85 		import core.time : Duration;
86 
87 		private Duration best;
88 		private bool benchmarked;
89 
90 		void benchmark(void delegate() dg)
91 		{
92 			assert(!benchmarked, "A block was already benchmarked during this benchmark.");
93 			benchmarked = true;
94 
95 			StopWatch sw;
96 			foreach (iteration; 0 .. iterations)
97 			{
98 				sw.start();
99 				dg();
100 				sw.stop();
101 				if (best > sw.peek())
102 					best = sw.peek();
103 				sw.reset();
104 			}
105 		}
106 	}
107 }