diff --git a/perf/benchmark.cjs b/perf/benchmark.cjs index 6fb38e9a..f133d65a 100644 --- a/perf/benchmark.cjs +++ b/perf/benchmark.cjs @@ -13,6 +13,7 @@ const { testDistribution } = require('./tasks.cjs'); const Benchmark = require('benchmark'); const prandRef = require('../lib/pure-rand'); const prandTest = require('../lib-new/pure-rand'); +const { countCallsToNext } = require('./helpers.cjs'); const PRERUN_SAMPLES = 50; const WARMUP_SAMPLES = 1000; @@ -23,71 +24,83 @@ const NUM_TESTS = 1000; const PROF_GEN = process.env.PROF_GEN || 'xoroshiro128plus'; console.log(`Generator: ${PROF_GEN}\n`); -// Declare builder of benchmarks -const buildBenchmarks = (type, lib) => { - return [ - new Benchmark( - `uniformIntDistribution[0;96]...............................@${type}`, - () => { - // Range size is prime - const g = genFor(lib, PROF_GEN); - const distribution = lib.uniformIntDistribution; - const settings = { min: 0, max: 0xffff }; - testDistribution(distribution, g, NUM_TESTS, settings); - }, - benchConf - ), - new Benchmark( - `uniformIntDistribution[0;0xffff]...........................@${type}`, - () => { - // Range size is a small power of 2 - const g = genFor(lib, PROF_GEN); - const distribution = lib.uniformIntDistribution; - const settings = { min: 0, max: 0xffff }; - testDistribution(distribution, g, NUM_TESTS, settings); - }, - benchConf - ), - new Benchmark( - `uniformIntDistribution[0;0xffffffff].......................@${type}`, - () => { - // For range of size <=2**32 (ie to-from+1 <= 2**32) - // uniformIntDistribution uses another execution path - const g = genFor(lib, PROF_GEN); - const distribution = lib.uniformIntDistribution; - const settings = { min: 0, max: 0xffffffff }; - testDistribution(distribution, g, NUM_TESTS, settings); - }, - benchConf - ), - new Benchmark( - `uniformIntDistribution[0;0xffffffff+1].....................@${type}`, - () => { - // Range size is just above threshold used by uniformIntDistribution - // to switch to another algorithm - const g = genFor(lib, PROF_GEN); - const distribution = lib.uniformIntDistribution; - const settings = { min: 0, max: 0xffffffff + 1 }; - testDistribution(distribution, g, NUM_TESTS, settings); - }, - benchConf - ), - new Benchmark( - `uniformIntDistribution[MIN_SAFE_INTEGER;MAX_SAFE_INTEGER]..@${type}`, - () => { - // Range size is the maximal one - const g = genFor(lib, PROF_GEN); - const distribution = lib.uniformIntDistribution; - const settings = { min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER }; - testDistribution(distribution, g, NUM_TESTS, settings); - }, - benchConf - ), - ]; -}; +// Declare configuration matrix +const configurations = [ + ['Reference', prandRef], + ['Test', prandTest], +]; -// Declare benchmarks -const benchmarks = [...buildBenchmarks('Reference', prandRef), ...buildBenchmarks('Test', prandTest)]; +// Declare performance tests +const performanceTests = [ + { + name: (type) => `uniformIntDistribution[0;96]...............................@${type}`, + run: (lib, customGen = genFor) => { + // Range size is prime + const g = customGen(lib, PROF_GEN); + const distribution = lib.uniformIntDistribution; + const settings = { min: 0, max: 0xffff }; + testDistribution(distribution, g, NUM_TESTS, settings); + }, + }, + { + name: (type) => `uniformIntDistribution[0;0xffff]...........................@${type}`, + run: (lib, customGen = genFor) => { + // Range size is a small power of 2 + const g = customGen(lib, PROF_GEN); + const distribution = lib.uniformIntDistribution; + const settings = { min: 0, max: 0xffff }; + testDistribution(distribution, g, NUM_TESTS, settings); + }, + }, + { + name: (type) => `uniformIntDistribution[0;0xffffffff].......................@${type}`, + run: (lib, customGen = genFor) => { + // For range of size <=2**32 (ie to-from+1 <= 2**32) + // uniformIntDistribution uses another execution path + const g = customGen(lib, PROF_GEN); + const distribution = lib.uniformIntDistribution; + const settings = { min: 0, max: 0xffffffff }; + testDistribution(distribution, g, NUM_TESTS, settings); + }, + }, + { + name: (type) => `uniformIntDistribution[0;0xffffffff+1].....................@${type}`, + run: (lib, customGen = genFor) => { + // Range size is just above threshold used by uniformIntDistribution + // to switch to another algorithm + const g = customGen(lib, PROF_GEN); + const distribution = lib.uniformIntDistribution; + const settings = { min: 0, max: 0xffffffff + 1 }; + testDistribution(distribution, g, NUM_TESTS, settings); + }, + }, + { + name: (type) => `uniformIntDistribution[MIN_SAFE_INTEGER;MAX_SAFE_INTEGER]..@${type}`, + run: (lib, customGen = genFor) => { + // Range size is the maximal one + const g = customGen(lib, PROF_GEN); + const distribution = lib.uniformIntDistribution; + const settings = { min: Number.MIN_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER }; + testDistribution(distribution, g, NUM_TESTS, settings); + }, + }, +]; + +// Declare the benchmarks +const benchmarks = configurations.flatMap(([type, lib]) => + performanceTests.map((test) => new Benchmark(test.name(type), () => test.run(lib), benchConf)) +); + +// Simple checks concerning number of calls to the underlying generators +console.log(`Measuring number of calls to next...\n`); +for (const test of performanceTests) { + for (const [type, lib] of configurations) { + const [g, counter] = countCallsToNext(genFor(lib, PROF_GEN)); + test.run(lib, () => g); + console.log(`${test.name(type)} called generator on next ${counter.count} times`); + } +} +console.log(``); // Run all the code of all the benchmarks at least once before running them for measurements. // It ensures that non-optimized path will not be wrongly optimized. In the past we had reports like: diff --git a/perf/helpers.cjs b/perf/helpers.cjs index 6f795eca..a662a4f2 100644 --- a/perf/helpers.cjs +++ b/perf/helpers.cjs @@ -5,3 +5,25 @@ exports.genFor = (lib, genName) => { const seed = 42; return lib[genName](seed); }; + +// Build a wrapper around a generator to count calls to next +exports.countCallsToNext = (gen) => { + const data = { count: 0 }; + class Wrapper { + constructor(g) { + this.g = g; + } + min() { + return this.g.min(); + } + max() { + return this.g.max(); + } + next() { + data.count += 1; + const [v, ng] = this.g.next(); + return [v, new Wrapper(ng)]; + } + } + return [new Wrapper(gen), data]; +}; diff --git a/perf/profiler.cjs b/perf/profiler.cjs index f7d2b21e..ea69bb52 100644 --- a/perf/profiler.cjs +++ b/perf/profiler.cjs @@ -9,8 +9,8 @@ const { testNext, testJump, testDistribution } = require('./tasks.cjs'); const prand = require('../lib/pure-rand'); const NUM_TESTS = 10000000; -const PROF_TYPE = process.env.PROF_TYPE || 'skip'; -const PROF_GEN = process.env.PROF_GEN || 'congruential32'; +const PROF_TYPE = process.env.PROF_TYPE || 'distrib'; +const PROF_GEN = process.env.PROF_GEN || 'xoroshiro128plus'; console.log(`Profiler type: ${PROF_TYPE}`); console.log(`Generator....: ${PROF_GEN}`); @@ -22,7 +22,13 @@ switch (PROF_TYPE) { case 'jump': testJump(g, NUM_TESTS); break; - case 'skip': + case 'distrib': testDistribution(prand.uniformIntDistribution, g, NUM_TESTS); break; + case 'distrib-large': + testDistribution(prand.uniformIntDistribution, g, NUM_TESTS, { + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + }); + break; }