1 ///Parameterize a benchmark 2 module testquark.parabench; 3 //An unfortunate requirement. 4 public import testquark.defines; 5 import std.range; 6 import std.traits; 7 import std.typecons; 8 import std.json; 9 import std.stdio; 10 /** Define a way of providing testing data to a benchmark, 11 * i.e. stop the compiler DFAing the magic away. It must be 12 * parameterized by a size_t such that it can be measured. 13 * 14 * You provide the generating algorithm and a range to specify 15 */ 16 struct ParameterizerStruct(Range, FuncType) 17 if (isInputRange!Range && is(ElementType!Range : size_t) && (isCallable!FuncType 18 && arity!FuncType == 1 && __traits(isSame, Parameters!FuncType[0], size_t))) 19 { 20 NamedBenchmark loc; 21 Range store; 22 FuncType func; 23 ///If you already have the location 24 this(NamedBenchmark l, Range set, FuncType fSet) 25 { 26 loc = l; 27 store = set; 28 func = fSet; 29 30 } 31 /// 32 this(string name, Range set, FuncType fSet) 33 { 34 //loc = NamedBenchmark(name, line, file); 35 store = set; 36 func = fSet; 37 38 } 39 40 } 41 ///Split up operation for exposing to afar 42 struct Operation 43 { 44 //Size of task 45 size_t size; 46 47 //This atomizes the actual task in hand so it can be timed/measured externally 48 void delegate() runIt; 49 invariant(runIt !is null); 50 } 51 ///Abstract range interface to a benchmark for some type erasure 52 interface BenchmarkPrototype : InputRange!(Operation) 53 { 54 @property Operation front(); 55 void popFront(); 56 bool empty(); 57 const size_t iterationsPerMeasurement(); 58 } 59 60 auto godawfulHack(RetType_, RangeType_, FuncType_, Operation_, alias Item_, I, J, K, L)(I x, J y, K z, L fuck) 61 { 62 return new class (x, y, z, fuck) BenchmarkPrototype 63 { 64 RetType_ actOnThisData; 65 RangeType_ theRange; 66 FuncType_ theFunction; 67 Operation_ current; 68 NamedBenchmark loc; 69 typeof(&Item_) benchFunc; 70 @property Operation front() 71 { 72 return current; 73 } 74 75 size_t iterationsPerMeasurement() const 76 { 77 return loc.iterations; 78 } 79 80 void generateData() 81 { 82 current.runIt = &runTask; 83 actOnThisData = theFunction(current.size); 84 85 } 86 87 void popFront() 88 { 89 //writeln("popped", theRange.empty); 90 theRange.popFront; 91 92 if(!theRange.empty) current.size = theRange.moveFront; 93 generateData(); 94 } 95 96 void runTask() 97 { 98 benchFunc(actOnThisData); 99 } 100 101 bool empty() 102 { 103 return theRange.empty; 104 } 105 Operation moveFront() 106 { 107 //writeln("moved"); 108 return current; 109 } 110 int opApply(scope int delegate(Operation) it) 111 { 112 //writeln("opapplied"); 113 while(!empty) 114 { 115 it(this.current); 116 if(!theRange.empty) this.popFront(); 117 } 118 return 0; 119 } 120 int opApply(scope int delegate(ulong, Operation)) 121 { 122 assert(0, "nonsensical construct"); 123 124 } 125 126 this(NamedBenchmark sLoc, RangeType_ set, FuncType_ task, typeof(&Item_) setB) 127 { 128 theFunction = task; 129 theRange = set; 130 loc = sLoc; 131 //current = Operation(theRange.front, &this.runTask); 132 benchFunc = setB; 133 generateData; 134 } 135 }; 136 } 137 auto MarkImpl(alias mod)(ref BenchmarkPrototype[NamedBenchmark] map) 138 { 139 140 void register(NamedBenchmark key, BenchmarkPrototype set) 141 { 142 map[key] = set; 143 } 144 foreach (sItem; __traits(allMembers, mod)) 145 { 146 147 import std.stdio; 148 149 alias Item = __traits(getMember, mod, sItem); 150 const theAttributes = __traits(getAttributes, Item); 151 //pragma(msg, sItem, " ", theAttributes.length); 152 //No attributes 153 static if (theAttributes.length & isCallable!Item) 154 { 155 156 foreach (attr; theAttributes) 157 { 158 if (is(typeof(attr) : Template!Args, alias Template, Args...)) 159 { 160 if (__traits(isSame, Template, ParameterizerStruct)) 161 { 162 //pragma(msg, attr); 163 //pragma(msg, Args); 164 alias RangeType = Args[0]; 165 alias FuncType = Args[1]; 166 167 alias RetType = ReturnType!FuncType; 168 //pragma(msg, sItem); 169 static assert(__traits(isSame, RetType, Parameters!Item[0]), 170 "Functioning being benchmarked is not callable with data provided by parameterizer"); 171 { 172 register(attr.loc, godawfulHack!(RetType, RangeType, FuncType, Operation, Item, typeof(attr.loc), typeof(attr.store), typeof(attr.func), typeof(&Item))(attr.loc, attr.store, attr.func, &Item)); 173 } 174 } 175 } 176 177 } 178 } 179 180 } 181 return map; 182 } 183 184 185 pragma(lib, "papi"); 186 187 auto runThem(Flag!"UsePapi" usePapi = Yes.UsePapi)(BenchmarkPrototype[NamedBenchmark] theBenchmarks, Flag!"FullData" fullDataView = No.FullData) 188 { 189 static if(usePapi) 190 { 191 pragma(msg, "Link with PAPI 5 for this to work, pragma(lib, \"papi\") should work\nSet a very low perf_paranoid"); 192 //pragma(lib, "papi"); 193 } 194 JSONValue runBenchmark(BenchmarkPrototype bench) 195 in(bench !is null, "Null pointer") 196 { 197 import std.conv : to; 198 ulong[string][string] data; 199 foreach (pair; bench) 200 { 201 long totalMeasured = 0; 202 foreach (i; 0 .. bench.iterationsPerMeasurement) 203 { 204 205 import std.datetime.stopwatch : StopWatch; 206 static if (usePapi) { 207 import testquark.papi; 208 import testquark.papiStdEventDefs; 209 210 enum numCounters = 1; 211 long[numCounters] values; 212 213 int[numCounters] eventSet = [PAPI_TOT_INS]; 214 215 if (PAPI_start_counters(eventSet.ptr, numCounters) != PAPI_OK) 216 assert(0, "Papi Initialization failed"); 217 218 pair.runIt(); 219 //if (PAPI_read_counters(values.ptr, numCounters) != PAPI_OK) 220 //assert(0, "Failed reading counters"); 221 222 if (PAPI_stop_counters(values.ptr, numCounters) != PAPI_OK) 223 assert(0, "Failing stopping counters?!"); 224 225 const long measured = values[0]; 226 } else { 227 auto sw = StopWatch(No.autoStart); 228 sw.start(); 229 pair.runIt(); 230 sw.stop(); 231 const long measured = sw.peek.total!"usecs"; 232 } 233 234 totalMeasured += measured; 235 if(fullDataView) 236 data[pair.size.to!string][i.to!string] = measured; 237 } 238 static if (usePapi) 239 { 240 enum name = "meaninstructions"; 241 } 242 else { 243 enum name = "meanUsecs"; 244 } 245 data[pair.size.to!string][name] = cast(ulong) (cast(float) totalMeasured / bench.iterationsPerMeasurement); 246 247 } 248 249 return JSONValue(data); 250 } 251 252 //writeln(theBenchmarks.length); 253 //theBenchmarks.length 254 ulong index = 0; 255 auto tmp = JSONValue(iota(index, theBenchmarks.length + 1).array); 256 foreach (NamedBenchmark key, value; theBenchmarks) 257 { 258 259 tmp[index].object(["fullDefinition" :key.toJson, "data": runBenchmark(value)]); 260 //tmp[key.name]["data"] = runBenchmark(value); 261 index += 1; 262 } 263 return tmp; 264 } 265 /** Mix this in to gain the power of benchmarks, if on auto they will run on module destruction 266 * 267 * Params: 268 * expose = Expose to benchquark proper 269 */ 270 template BenchmarkInfrastructure(Flag!"Expose" expose = No.Expose, string M = __MODULE__) 271 { 272 BenchmarkPrototype[NamedBenchmark] benchmarks; 273 void setup() 274 { 275 benchmarks = MarkImpl!(mixin(M))(benchmarks); 276 } 277 void runAndPrint(Flag!"UsePapi" usePapi)() 278 { 279 setup(); 280 runThem!usePapi(benchmarks).toPrettyString.writeln; 281 } 282 } 283 284 ///Use these (unless you want to manually specify range types into ParameterizeStruct) 285 public auto Parameterizer(Range, FuncType)(string name, Range x, FuncType func, 286 size_t iter = 1, string f = __FILE__, int l = __LINE__) 287 { 288 return Parameterizer!(Range, FuncType)(NamedBenchmark(name, iter, l, f), x, func); 289 } 290 ///Use these (unless you want to manually specify range types into ParameterizeStruct) 291 public auto Parameterizer(Range, FuncType)(NamedBenchmark loc, Range x, FuncType func) 292 { 293 return ParameterizerStruct!(Range, FuncType)(loc, x, func); 294 } 295 ///A simple benchmark involving specifying a sorting function 296 297 298 299