| // Benchmarking script. This runs on compiled bench.wat and prints out timings. | |
| // | |
| // Usage: | |
| // | |
| // * wasm-opt scripts/benchmarking/bench.wat -all --inline-functions-with-loops --always-inline-max-function-size=1000 --inlining --precompute-propagate --optimize-instructions --inlining --simplify-locals --coalesce-locals --vacuum --remove-unused-module-elements -o bench.wasm -g | |
| // * Inspect the optimized wasm to see that inlining etc. worked properly | |
| // (we rely on inlining to let us write bench.wat in a short/simple form, and | |
| // we use very specific optimizations in order to not optimize away the | |
| // differences we care about). | |
| // * d8 bench.js -- bench.wasm | |
| // etc. | |
| // | |
| // Shell integration. | |
| if (typeof console === 'undefined') { | |
| console = { log: print }; | |
| } | |
| var tempRet0; | |
| var binary; | |
| if (typeof process === 'object' && typeof require === 'function' /* node.js detection */) { | |
| var args = process.argv.slice(2); | |
| binary = require('fs').readFileSync(args[0]); | |
| if (!binary.buffer) binary = new Uint8Array(binary); | |
| } else { | |
| var args; | |
| if (typeof scriptArgs != 'undefined') { | |
| args = scriptArgs; | |
| } else if (typeof arguments != 'undefined') { | |
| args = arguments; | |
| } | |
| if (typeof readbuffer === 'function') { | |
| binary = new Uint8Array(readbuffer(args[0])); | |
| } else { | |
| binary = read(args[0], 'binary'); | |
| } | |
| } | |
| // Create the wasm. | |
| const module = new WebAssembly.Module(binary); | |
| const instance = new WebAssembly.Instance(module, {}); | |
| const exports = instance.exports; | |
| // Create the benchmarkers. | |
| function makeBenchmarker(name) { | |
| return { | |
| name: name, | |
| func: exports[name], | |
| time: 0, | |
| sum: 0, | |
| iters: 0, | |
| }; | |
| } | |
| const benchmarkers = [ | |
| makeBenchmarker('len'), | |
| makeBenchmarker('and'), | |
| makeBenchmarker('iff-both'), | |
| makeBenchmarker('or'), | |
| makeBenchmarker('iff-either'), | |
| makeBenchmarker('select'), | |
| makeBenchmarker('iff-nextor'), | |
| makeBenchmarker('select-three'), | |
| makeBenchmarker('iff-three'), | |
| ]; | |
| // We'll call the benchmark functions in random orders. Random orders avoid any | |
| // interaction between the benchmarks from causing bias in the results. | |
| // | |
| // An alternative to randomly ordering the benchmarks in each iteration would be | |
| // to fully benchmark one, then do the next, so there are large amounts of time | |
| // between them, but that also allows them to become more like microbenchmarks | |
| // where the branch predictor etc. might display very favorable behavior. | |
| // Interleaving them makes things slightly more realistic. | |
| // | |
| // If we have too many benchmarks then eventually computing all orders ahead of | |
| // time will not work, but so long as we can, it is faster this way rather than | |
| // to compute random orders on the fly as we go. | |
| function makeOrders(prefix) { | |
| // Given a prefix of an order, like [] or [0, 3], return all the possible | |
| // orders beginning with that prefix. | |
| // We cannot repeat anything already seen. | |
| const seen = new Set(); | |
| for (var x of prefix) { | |
| seen.add(x); | |
| } | |
| // Starting from the prefix, extend it by one item in all valid ways. | |
| const extensions = []; | |
| for (var i = 0; i < benchmarkers.length; i++) { | |
| if (!seen.has(i)) { | |
| extensions.push(prefix.concat(i)); | |
| } | |
| } | |
| if (prefix.length == benchmarkers.length - 1) { | |
| // The extensions are complete orders; stop the recursion. | |
| return extensions; | |
| } | |
| // Recursively generate the full orders. | |
| const ret = []; | |
| for (var extension of extensions) { | |
| for (var order of makeOrders(extension)) { | |
| ret.push(order); | |
| } | |
| } | |
| return ret; | |
| } | |
| const orders = makeOrders([]); | |
| // Params. | |
| const M = 10000000; | |
| const N = 100; | |
| console.log('iters :', M); | |
| console.log('list len :', N); | |
| console.log('benchmarkers:', benchmarkers.length); | |
| console.log('orderings :', orders.length); | |
| // Create a long linked list of objects of both type $A and $B. | |
| var list = null; | |
| for (var i = 0; i < N; i++) { | |
| list = Math.random() < 0.5 ? exports.makeA(list) : exports.makeB(list); | |
| } | |
| console.log('benchmarking...'); | |
| // Call the benchmark functions. | |
| for (var i = 0; i < M; i++) { | |
| const order = orders[Math.floor(Math.random() * orders.length)]; | |
| for (var k = 0; k < benchmarkers.length; k++) { | |
| const benchmarker = benchmarkers[order[k]]; | |
| const start = performance.now(); | |
| const result = benchmarker.func(list); | |
| benchmarker.time += performance.now() - start; | |
| benchmarker.sum += result; | |
| benchmarker.iters++; | |
| } | |
| } | |
| for (var benchmarker of benchmarkers) { | |
| if (benchmarker.iters != M) { | |
| throw 'wat'; | |
| } | |
| } | |
| console.log(); | |
| for (var benchmarker of benchmarkers) { | |
| console.log(`${benchmarker.name} time: \t${benchmarker.time}`) | |
| } | |
| console.log(); | |
| for (var benchmarker of benchmarkers) { | |
| console.log(`${benchmarker.name} mean sum: \t${benchmarker.sum / M}`) | |
| } | |