Last active
August 10, 2018 04:31
-
-
Save piscisaureus/fe831dabf7bea47752c1dc6a4d5cadce to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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