1 module chimpfella.execute; 2 import chimpfella.kernels; 3 import std.meta; 4 5 ///Information from you about what to do with your benchmarks, where to put them, and what to do in between them. 6 public struct MetaExecution(ProcessBench) 7 { 8 ///Output range to deal with the data generated (i.e. store in a file, dump to stdout etc.) 9 ProcessBench storeData; 10 this(ProcessBench sendSet) 11 { 12 storeData = sendSet; 13 } 14 //The output range is generic, so it can be from you but could also be dispatched at runtime via an interface 15 ///What to do with the GC 16 struct GCActions 17 { 18 19 } 20 // 21 } 22 23 /+++ 24 Execution Engine: 25 Takes multiple functions to be executed, however, they should be considered as a group, use multiple engines 26 for totally distinct functions. 27 +/ 28 private template DefaultExecutionEngine(string fullName, FuncPack...) 29 if (FuncPack.length > 0) 30 { 31 alias theFunc = FuncPack[0]; 32 import chimpfella.measurement; 33 34 void runEngine(MetaT, alias genInd, alias indToData)( 35 scope ref MetaT metaExecutionState, const Measurements[] runThese) 36 { 37 import std.traits : isCallable; 38 39 //start at one so we can store the independant variable 40 size_t width = 1; 41 foreach (meas; runThese) 42 { 43 width += procMeas!(m => m.eventCount())(meas); 44 } 45 size_t[] resultBuffer; 46 { 47 import core.stdc.stdlib : alloca; 48 49 void* x = alloca(width * size_t.sizeof); 50 resultBuffer = (cast(size_t*) x)[0 .. width]; 51 resultBuffer[] = 0; 52 } 53 static if (isCallable!genInd) 54 auto indData = genInd(); 55 else 56 auto indData = genInd; 57 58 auto outputDataHere = metaExecutionState.storeData.getOutputHandle(fullName, runThese); 59 foreach (indItem; indData) 60 { 61 resultBuffer[0] = indItem; 62 //If generator function is void, give the independant variable straight to the function 63 static if (is(indToData == void)) 64 const theData = indItem; 65 else 66 const theData = indToData(indItem); 67 //Iterate over each measurement, but we don't yet consider the cost of each one. 68 foreach (i, counter; runThese) 69 { 70 const measurementWidth = procMeas!(m => m.eventCount())(counter); 71 //How many results have been given so far 72 size_t countsRetired = 0; 73 scope (exit) 74 countsRetired += measurementWidth; 75 import std.traits : ReturnType; 76 77 StateTypes state = counter.getState; 78 procState!((ref x) => x.start)(state); 79 //Hot stuff starts here 80 static if (__traits(isSame, ReturnType!theFunc, void)) 81 { 82 //throw away, nothing we can do 83 theFunc(theData); 84 //Hot region ends 85 procState!((ref x) => x.stop)(state); 86 } 87 else 88 { 89 import std.traits : arity, hasMember; 90 91 static if (arity!theFunc > 1) 92 { 93 94 static assert(hasMember!(typeof(theData), "pack"), 95 "Since D does not have multiple return values this library requires that you return some\n 96 type containing 97 an AliasSeq of values named \"pack\" which the library will expand for you"); 98 const res = theFunc(theData.pack); 99 } 100 else 101 { 102 const res = theFunc(theData); 103 } 104 105 //Hot region ends 106 procState!((ref x) => x.stop)(state); 107 //force a data dependancy on the output to trick the compiler 108 import core..volatile; 109 110 ubyte[res.sizeof] outputVolatile; 111 ubyte[] slicedInput = (cast(ubyte*)&res)[0 .. res.sizeof]; 112 foreach (__i, ref d; slicedInput) 113 { 114 volatileStore(&outputVolatile[__i], d); 115 } 116 } 117 118 //We have the data, do something with it. 119 { 120 import std.array : RefAppender, appender; 121 122 const startIdx = 1 + countsRetired; 123 auto app = resultBuffer[startIdx .. startIdx + measurementWidth]; 124 125 procState!((ref x) => x.read(app))(state); 126 } 127 128 outputDataHere.put(resultBuffer); 129 } 130 131 } 132 } 133 134 } 135 ///Execute benchmarks from `fromHere` - i.e. this runs ExecutionEngine for you all those Benchmarks 136 template ExecuteBenchmarks(alias fromHere) 137 { 138 import std.traits; 139 import std.range : ElementType; 140 141 void run(MetaT)(scope ref MetaT metaExecutionState) 142 { 143 alias udaList = getSymbolsByUDA!(fromHere, FunctionBenchmark); 144 //Dispatch based on what other Benchmark UDAs we find. 145 foreach (symbol; udaList) 146 { 147 //Symbol has a FunctionBenchmark 148 alias funcBenchPack = getUDAs!(symbol, FunctionBenchmark); 149 //If there is anything other than one the benchmarkark is ill formed 150 static assert(funcBenchPack.length == 1); 151 enum theFunctionBench = funcBenchPack[0]; 152 //Is it a template? 153 static if (__traits(isTemplate, symbol)) 154 { 155 //Should have a TemplateBenchmark attached to it to tell us what metaparameters to use. 156 157 //Data generator is a template; We must instantiate it. 158 enum generatorIsATemplate = __traits(isTemplate, theFunctionBench.genData); 159 160 alias templateMetaPack = getUDAs!(symbol, TemplateBenchmark); 161 const uint paramCov = templateMetaPack.length; 162 //We need at least one 163 static assert(paramCov, "there is a function benchmark attached to a symbol that is a template - it does not have a TemplateBenchmark"); 164 enum order(alias left, alias right) = left.index < right.index; 165 //Get 'em in the right order 166 167 alias sortedMetaPack = staticSort!(order, templateMetaPack); 168 //Doesn't do every permutation (yet) 169 foreach (packIdx, packItem; sortedMetaPack) 170 { 171 //Process a single parameter 172 foreach (innerIdx, paramItem; packItem.paramPack) 173 { 174 import std.range.primitives; 175 176 //pragma(msg, "\t", paramItem); 177 //Work out what we're doing, then instantiate theFunc with it. 178 //Is it a type 179 180 //Parameter is fits the slot 181 static if (__traits(compiles, symbol!paramItem)) 182 { 183 alias func = symbol!paramItem; 184 185 auto bench = theFunctionBench; 186 //static assert(!is(ReturnType!symbol == void), "benchmarked function may not (yet) be void. Support for ref args coming soon"); 187 //Run the benchmark 188 alias engine = DefaultExecutionEngine!(theFunctionBench.benchmarkName ~ paramItem.stringof, 189 func); 190 enum forwardTemplateParam = bench.forward; 191 alias genInd = bench.genIndSet; 192 alias genData_ = bench.genData; 193 alias IndT = ElementType!(typeof(genInd)); 194 195 static if (__traits(isTemplate, genData_)) 196 { 197 static assert(__traits(compiles, genData_!IndT)); 198 alias genFunc = genData_!IndT; 199 } 200 else 201 { 202 static if (forwardTemplateParam) 203 { 204 alias genFunc = genData_.contents!paramItem; 205 } 206 else 207 { 208 alias genFunc = genData_; 209 } 210 211 } 212 213 engine.runEngine!(MetaT, genInd, 214 genFunc)(metaExecutionState, bench.measurementList); 215 } 216 else 217 { 218 //It's not a type, so it better be a range literal. 219 //Arrays are special cased 220 alias specRangeT = typeof(paramItem); 221 //pragma(msg, isInputRange!specRangeT); 222 static assert(isInputRange!specRangeT 223 && !isInfinite!(specRangeT), "not a valid parameter"); 224 //It's a range 225 alias ElemT = ElementType!specRangeT; 226 //pragma(msg, ElemT); 227 228 static assert(__traits(compiles, symbol!(ElemT.init)), 229 " symbol = " ~ fullyQualifiedName!symbol); 230 import std.array : array; 231 enum ctRange = paramItem.array; 232 233 static foreach (ctRangeItem; ctRange) 234 { 235 { 236 alias func = symbol!ctRangeItem; 237 238 auto bench = theFunctionBench; 239 static if(isSomeString!(typeof(ctRangeItem))) 240 enum stringSummary = ctRangeItem; 241 else 242 enum stringSummary = ctRangeItem.stringof; 243 //Run the benchmark 244 alias engine = DefaultExecutionEngine!( 245 theFunctionBench.benchmarkName ~ stringSummary, func); 246 enum forwardTemplateParam = bench.forward; 247 alias genInd = bench.genIndSet; 248 alias genData_ = bench.genData; 249 alias IndT = ElementType!(typeof(genInd)); 250 251 static if (__traits(isTemplate, genData_)) 252 { 253 static assert(__traits(compiles, genData_!IndT)); 254 alias genFunc = genData_!IndT; 255 } 256 else 257 { 258 static if (forwardTemplateParam) 259 { 260 alias genFunc = genData_.contents!paramItem; 261 } 262 else 263 { 264 alias genFunc = genData_; 265 } 266 } 267 engine.runEngine!(MetaT, genInd, 268 genFunc)(metaExecutionState, bench.measurementList); 269 } 270 271 } 272 273 } 274 275 } 276 } 277 //A parameter can be a range, don't forget 278 279 //Can't introspect over the number of template parameters realistically. 280 } 281 else 282 { 283 //It's a function benchmark 284 //To find out how many there are 285 286 auto bench = theFunctionBench; 287 //static assert(!is(ReturnType!symbol == void), "benchmarked function may not (yet) be void. Support for ref args coming soon"); 288 //Run the benchmark 289 alias engine = DefaultExecutionEngine!(theFunctionBench.benchmarkName, symbol); 290 alias genInd = bench.genIndSet; 291 alias genData_ = bench.genData; 292 alias IndT = ElementType!(typeof(genInd)); 293 294 static if (__traits(isTemplate, genData_)) 295 { 296 static assert(__traits(compiles, genData_!IndT)); 297 alias genFunc = genData_!IndT; 298 } 299 else 300 { 301 alias genFunc = genData_; 302 } 303 304 engine.runEngine!(MetaT, genInd, genFunc)(metaExecutionState, 305 bench.measurementList); 306 } 307 308 } 309 310 } 311 312 } 313 314 /// 315 unittest 316 { 317 @trusted auto getStdoutRange() 318 { 319 import std.stdio : stdout; 320 321 return stdout.lockingTextWriter; 322 } 323 //Lump the benchmarks together somewhere. 324 static class StaticStore 325 { 326 327 import chimpfella.kernels; 328 import chimpfella.measurement; 329 import std.range : iota, repeat; 330 import std.random : uniform; 331 332 enum meas = [PhobosTimer("A stub").toMeasurement]; 333 334 @FunctionBenchmark!("Something with an integer", iota(1, 50), (x) => uniform(-x, x))(meas) static void func( 335 int l) 336 { 337 uint x; 338 import core..volatile; 339 340 foreach (_; 0 .. l) 341 volatileStore(&x, _); 342 } 343 344 static auto getRandPair(T)(int x) 345 { 346 import std.random; 347 348 struct rndParams 349 { 350 AliasSeq!(T, T) pack; 351 } 352 353 rndParams tmp; 354 auto rnd = Random(unpredictableSeed); 355 356 // Generate an integer in [0, 1023] 357 tmp.pack[0] = cast(T) uniform(0, 1024, rnd); 358 tmp.pack[1] = cast(T) uniform(0, 1024, rnd); 359 return tmp; 360 } 361 362 @TemplateBenchmark!(0, int, float, double) @FunctionBenchmark!("Templated add benchmark", 363 0.repeat(100), ForwardTemplate!(getRandPair))(meas) static T templatedAdd(T)(T x, 364 T y) 365 { 366 return x + y; 367 } 368 import std.algorithm; 369 import std.array; 370 static string ctfeRepeater(int n) 371 { 372 return "cpuid;".repeat(n).join(); 373 } 374 375 enum cpuidRange = iota(1, 10).map!(ctfeRepeater).array; 376 @TemplateBenchmark!(0, cpuidRange) 377 @FunctionBenchmark!("Measure", iota(1, 10), (_) => [1, 2, 3, 4])(meas) 378 static int sum(string asmLine)(inout int[] input) 379 { 380 //This is quite fun because ldc will sometimes get rid of the entire function body and just loop over the asm's 381 int tmp; 382 foreach (i; input) 383 { 384 mixin("asm { ", asmLine, ";}"); 385 } 386 return tmp; 387 } 388 389 alias summat = sum!"mfence"; 390 } 391 392 @safe struct PrintData 393 { 394 import chimpfella.measurement; 395 396 auto getOutputHandle(string benchmarkName, scope const Measurements[] counters) 397 { 398 struct outputVoldy 399 { 400 string name; 401 size_t width; 402 import std; 403 404 //You don't have to do anything with the header 405 this(string setName, scope const Measurements[] them) 406 { 407 name = setName; 408 writefln!"---------------%s---------------"(setName); 409 auto counterPut = counters.map!(x => x.getHeader); 410 //pragma(msg, ElementType!(typeof(counterPut))); 411 writeln("I;", counterPut.joiner!(typeof(counterPut)).joiner.joiner(";")); 412 } 413 414 void put(scope size_t[] data) 415 { 416 data.map!(s => s.to!string).joiner(";").writeln; 417 } 418 419 } 420 421 return outputVoldy(benchmarkName, counters); 422 } 423 } 424 425 PrintData outBuf; 426 auto dataOutput = MetaExecution!PrintData(outBuf); 427 ExecuteBenchmarks!(StaticStore).run(dataOutput); 428 }