Skip to content

Instantly share code, notes, and snippets.

@piscisaureus
Last active August 10, 2018 04:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piscisaureus/fe831dabf7bea47752c1dc6a4d5cadce to your computer and use it in GitHub Desktop.
Save piscisaureus/fe831dabf7bea47752c1dc6a4d5cadce to your computer and use it in GitHub Desktop.
const {
openSync,
closeSync,
read,
readSync,
unlinkSync,
writeFileSync
} = require("fs");
const { tmpdir } = require("os");
const { resolve } = require("path");
const { promisify } = require("util");
const readAsync = promisify(read);
const CONCURRENCY_VALUES = [0, 1, 2, 4, 8, 16, 32, 64, 128]; // # Concurrency values to benchmark.
const BENCH_ROUNDS = 5; // Number of benchmark rounds.
const BENCH_TIME = 2; // Duraion of a single benchmark, in seconds.
const TIME_CHECKS = 5; // Check the time N times during benchmarking.
const GUESSED_RATE = 10000; // Before we know better, assume N ops/sec.
const READ_LENGTH = 1; // The number of bytes to read from the file.
async function main() {
for (let r = 0; r < BENCH_ROUNDS; r++) {
for (let c of CONCURRENCY_VALUES) {
if (c == 0) benchSync(r);
else await benchAsync(r, c);
}
}
}
function benchSync(round) {
const interval_time = BENCH_TIME / TIME_CHECKS;
let ops = 0; // Ops completed so far.
let elapsed = 0; // In seconds.
let rate = GUESSED_RATE; // In op/second.
print(`round #${round} :: sync ${align()}`);
let start = process.hrtime(); // In [seconds, nanoseconds].
const fd = acquireTestFd();
let buf = new Uint8Array(READ_LENGTH);
for (let interval = 0; interval < TIME_CHECKS; interval++) {
// Estimate how many ops to do complete before the next time check.
const ops_target = rate * interval_time * (interval + 1);
// Do ops until target is reached.
while (ops < ops_target) {
readSync(fd, buf, 0, buf.byteLength, 0);
ops++;
}
// Recompute stats.
const now = process.hrtime();
elapsed = now[0] - start[0] + (now[1] - start[1]) / 1e9;
rate = ops / elapsed;
// For debugging.
// printStats(ops, elapsed, rate, `interval #${interval}`)
}
releaseTestFd(fd);
printStats(ops, elapsed, rate);
}
async function benchAsync(round, concurrency) {
const interval_time = BENCH_TIME / TIME_CHECKS;
let ops = 0; // Ops completed so far.
let time_checks_left = 0;
let elapsed = 0; // In seconds.
let rate = GUESSED_RATE; // In op/second.
let interval = -1; // Increments until it reaches TIME_CHECKS. -1 = warm-up.
let ops_target = rate * interval_time; // #ops at the end of the 1st interval.
print(`round #${round} :: async(${concurrency})${align(concurrency)}`);
let start = process.hrtime(); // In [seconds, nanoseconds].
const threads = new Array(concurrency).fill(null).map(thread);
await Promise.all(threads);
printStats(ops, elapsed, rate);
async function thread() {
const fd = acquireTestFd();
let buf = new Uint8Array(READ_LENGTH);
for (;;) {
// Execute ops in a loop until the intermediate target is reached.
while (ops < ops_target) {
await readAsync(fd, buf, 0, buf.byteLength, 0);
++ops;
}
// Exit if another thread already completed the last interval.
if (interval == TIME_CHECKS) {
break;
}
// Recompute stats.
const now = process.hrtime();
elapsed = now[0] - start[0] + (now[1] - start[1]) / 1e9;
rate = ops / elapsed;
// For debugging.
// printStats(ops, elapsed, rate, `interval #${interval}`)
// Begin next interval.
if (++interval == TIME_CHECKS) {
break; // Benchmark is done.
}
// Recompute ops_target.
ops_target = rate * interval_time * (interval + 1);
}
releaseTestFd(fd);
}
}
function print(s) {
process.stdout.write(s);
}
function printStats(ops, elapsed, rate, prefix) {
print(
[
prefix,
`ops: ${ops.toFixed(0).padStart(6)}`,
`time: ${elapsed.toFixed(4).padStart(6)}s`,
`rate: ${rate.toFixed(0).padStart(6)}/s\n`
]
.map(s => s && ` ${s}`)
.join("")
);
}
function align(concurrency = 0) {
let nspaces = String(Math.max(...CONCURRENCY_VALUES)).length;
if (concurrency) {
nspaces -= String(concurrency).length;
}
return " ".repeat(nspaces);
}
let free_test_file_fds = [];
let test_files = [];
let test_file_counter = 0;
let unique = Math.random()
.toString(36)
.slice(2);
function createTestFile() {
const id = ++test_file_counter;
const filename = resolve(tmpdir(), `tpbench${id}.${unique}.tmp`);
writeFileSync(filename, "x".repeat(READ_LENGTH));
const fd = openSync(filename, "r");
test_files.push({ fd, filename });
return fd;
}
function destroyTestFile({ fd, filename }) {
closeSync(fd);
unlinkSync(filename);
}
function destroyTestFiles() {
test_files.forEach(destroyTestFile);
test_files = [];
free_test_file_fds = [];
}
function acquireTestFd() {
if (free_test_file_fds.length > 0) {
return free_test_file_fds.pop();
} else {
return createTestFile();
}
}
function releaseTestFd(fd) {
free_test_file_fds.push(fd);
}
process.on("exit", destroyTestFiles);
process.on("SIGINT", () => process.exit());
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment