Skip to content

Commit

Permalink
Merge df4d264 into 538c008
Browse files Browse the repository at this point in the history
  • Loading branch information
dubzzz committed Oct 29, 2020
2 parents 538c008 + df4d264 commit 9309fad
Show file tree
Hide file tree
Showing 13 changed files with 854 additions and 171 deletions.
27 changes: 27 additions & 0 deletions build-another.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Forbidden if something has not been commited
if ! git diff-index --quiet HEAD --; then
echo "Please commit your local changes!"
exit 1
fi

# Extract details concerning the current environnment
rawCurrentBranch="$(git branch | grep "*")"
currentBranch="$(expr substr "${rawCurrentBranch}" 3 1000)"

# Move to commit and build it
commitId=$1
if [ -z "$commitId" ]
then
echo "Please provide a commit id!"
exit 1
fi
target=$2
if [ -z "$target" ]
target="es6"
fi
git checkout "${commitId}"
yarn
./node_modules/typescript/bin/tsc --target "${target}" --outDir "lib-${commitId}/"

# Move back to original location
git checkout "${currentBranch}"
146 changes: 102 additions & 44 deletions perf/benchmark.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,120 @@
// $: PROF_GEN="mersenne" node perf/benchmark.cjs

const { genFor } = require('./helpers.cjs');
const {
testGenerateWithSameDistribution,
testGenerateWithSkipDistributionSingle,
testGenerateWithSkipDistribution,
} = require('./tasks.cjs');
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 WARMUP_SAMPLES = 50;
const MIN_SAMPLES = 250;
const PRERUN_SAMPLES = 50;
const WARMUP_SAMPLES = 1000;
const MIN_SAMPLES = 1000;
const benchConf = { initCount: WARMUP_SAMPLES, minSamples: MIN_SAMPLES };

const NUM_TESTS = 100;
const PROF_GEN = process.env.PROF_GEN || 'congruential32';
console.log(`Generator....: ${PROF_GEN}\n`);
const NUM_TESTS = 1000;
const PROF_GEN = process.env.PROF_GEN || 'xoroshiro128plus';
console.log(`Generator: ${PROF_GEN}\n`);

const buildBenchmarks = (type, lib) => {
return [
new Benchmark(
`distribution/no@${type}`,
() => {
const g = genFor(lib, PROF_GEN);
testGenerateWithSkipDistribution(lib, g, NUM_TESTS);
},
benchConf
),
new Benchmark(
`distribution/re-use@${type}`,
() => {
const g = genFor(lib, PROF_GEN);
testGenerateWithSameDistribution(lib, g, NUM_TESTS);
},
benchConf
),
new Benchmark(
`generator/new@${type}`,
() => {
const g = genFor(lib, PROF_GEN);
testGenerateWithSkipDistributionSingle(lib, g);
},
benchConf
),
];
};
// Declare configuration matrix
const configurations = [
['Reference', prandRef],
['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:
// test1 @reference - 400 ops/s
// test2 @reference - 200 ops/s
// test1 @reference - 200 ops/s
// test2 @reference - 200 ops/s
// Because running test2 de-optimized the code that was optimized for test1 during first runs.
console.log(`Warm-up phase...\n`);
Benchmark.invoke(
[
...buildBenchmarks('Reference', prandRef),
...buildBenchmarks('Test', prandTest),
...buildBenchmarks('Reference', prandRef),
...buildBenchmarks('Test', prandTest),
],
benchmarks.map((b) => b.clone({ initCount: 1, minSamples: PRERUN_SAMPLES })),
{
name: 'run',
queued: true,
onCycle: (event) => console.log(String(event.target)),
}
);

// Run benchmarks
console.log(`\nBenchmark phase...\n`);
Benchmark.invoke(benchmarks, {
name: 'run',
queued: true,
onCycle: (event) => console.log(String(event.target)),
});
22 changes: 22 additions & 0 deletions perf/helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
22 changes: 10 additions & 12 deletions perf/profiler.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@
// $: PROF_TYPE=next PROF_GEN=xoroshiro128plus node --prof --no-logfile-per-isolate --trace-deopt --trace-opt-verbose perf/profiler.cjs
// $: node --prof-process v8.log > v8.out
const { genFor } = require('./helpers.cjs');
const {
testNext,
testJump,
testGenerateWithSameDistribution,
testGenerateWithSkipDistribution,
} = require('./tasks.cjs');
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}`);

Expand All @@ -27,10 +22,13 @@ switch (PROF_TYPE) {
case 'jump':
testJump(g, NUM_TESTS);
break;
case 'same':
testGenerateWithSameDistribution(prand, g, NUM_TESTS);
case 'distrib':
testDistribution(prand.uniformIntDistribution, g, NUM_TESTS);
break;
case 'skip':
testGenerateWithSkipDistribution(prand, g, NUM_TESTS);
case 'distrib-large':
testDistribution(prand.uniformIntDistribution, g, NUM_TESTS, {
min: Number.MIN_SAFE_INTEGER,
max: Number.MAX_SAFE_INTEGER,
});
break;
}
23 changes: 5 additions & 18 deletions perf/tasks.cjs
Original file line number Diff line number Diff line change
@@ -1,38 +1,25 @@
// @ts-check

// Call next multiple times on the passed generator
/** Call next multiple times on the passed generator */
exports.testNext = (g, NUM_TESTS) => {
for (let idx = 0; idx !== NUM_TESTS; ++idx) {
g = g.next()[1];
}
return g;
};

// Cell jump multiple times on the passed generator
/** Call jump multiple times on the passed generator */
exports.testJump = (g, NUM_TESTS) => {
for (let idx = 0; idx !== NUM_TESTS; ++idx) {
g = g.jump();
}
return g;
};

// Avoid the construction of an intermediate distribution
exports.testGenerateWithSkipDistributionSingle = (lib, g) => {
return lib.uniformIntDistribution(0, 0xffffffff, g)[1];
};
exports.testGenerateWithSkipDistribution = (lib, g, NUM_TESTS) => {
for (let idx = 0; idx !== NUM_TESTS; ++idx) {
g = exports.testGenerateWithSkipDistributionSingle(lib, g);
}
return g;
};

// Build the distribution once
// then generate multiple values using it
exports.testGenerateWithSameDistribution = (lib, g, NUM_TESTS) => {
const dist = lib.uniformIntDistribution(0, 0xffffffff);
/** Avoid the construction of an intermediate distribution */
exports.testDistribution = (distribution, g, NUM_TESTS, settings = { min: 0, max: 0xffffffff }) => {
for (let idx = 0; idx !== NUM_TESTS; ++idx) {
g = dist(g)[1];
g = distribution(settings.min, settings.max, g)[1];
}
return g;
};
45 changes: 38 additions & 7 deletions src/distribution/UniformIntDistribution.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
import Distribution from './Distribution';
import RandomGenerator from '../generator/RandomGenerator';
import { uniformIntDistributionInternal } from './internals/UniformIntDistributionInternal';
import { ArrayInt64, fromNumberToArrayInt64, substractArrayInt64, toNumber } from './internals/ArrayInt';
import { uniformArrayIntDistributionInternal } from './internals/UniformArrayIntDistributionInternal';

function uniformIntInternal(from: number, rangeSize: number, rng: RandomGenerator): [number, RandomGenerator] {
const g = uniformIntDistributionInternal(rangeSize, rng);
g[0] += from;
return g;
const sharedA: ArrayInt64 = { sign: 1, data: [0, 0] };
const sharedB: ArrayInt64 = { sign: 1, data: [0, 0] };
const sharedC: ArrayInt64 = { sign: 1, data: [0, 0] };
const sharedD: ArrayInt64 = { sign: 1, data: [0, 0] };

function uniformIntInternal(from: number, to: number, rng: RandomGenerator): [number, RandomGenerator] {
const rangeSize = to - from;
if (rangeSize <= 0xffffffff) {
// Calling uniformIntDistributionInternal can be considered safe
// up-to 2**32 values. Above this range it may miss values.
const g = uniformIntDistributionInternal(rangeSize + 1, rng);
g[0] += from;
return g;
}

const rangeSizeArrayIntValue =
rangeSize <= Number.MAX_SAFE_INTEGER
? fromNumberToArrayInt64(sharedC, rangeSize) // no possible overflow given rangeSize is in a safe range
: substractArrayInt64(sharedC, fromNumberToArrayInt64(sharedA, to), fromNumberToArrayInt64(sharedB, from)); // rangeSize might be incorrect, we compute a safer range

// Adding 1 to the range
if (rangeSizeArrayIntValue.data[1] === 0xffffffff) {
// rangeSizeArrayIntValue.length === 2 by construct
// rangeSize >= 0x00000001_00000000 and rangeSize <= 0x003fffff_fffffffe
// with Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER = 0x003fffff_fffffffe
rangeSizeArrayIntValue.data[0] += 1;
rangeSizeArrayIntValue.data[1] = 0;
} else {
rangeSizeArrayIntValue.data[1] += 1;
}

sharedD.sign = 1;
const g = uniformArrayIntDistributionInternal(sharedD.data, rangeSizeArrayIntValue.data, rng);
return [from + toNumber(sharedD), g[1]];
}

/**
Expand All @@ -28,12 +60,11 @@ function uniformIntDistribution(from: number, to: number): Distribution<number>;
*/
function uniformIntDistribution(from: number, to: number, rng: RandomGenerator): [number, RandomGenerator];
function uniformIntDistribution(from: number, to: number, rng?: RandomGenerator) {
const rangeSize = to - from + 1;
if (rng != null) {
return uniformIntInternal(from, rangeSize, rng);
return uniformIntInternal(from, to, rng);
}
return function (rng: RandomGenerator) {
return uniformIntInternal(from, rangeSize, rng);
return uniformIntInternal(from, to, rng);
};
}

Expand Down
Loading

0 comments on commit 9309fad

Please sign in to comment.