From b505e143440933f832b01621d380bdf5e59fe172 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Tue, 21 Sep 2021 02:03:15 +0200 Subject: [PATCH] Add iterate and clear benchmarks Undocumented for now because they're in POC stage. --- benchmarks/clear.js | 122 ++++++++++++++++++++++++++++++ benchmarks/clear.plot.js | 55 ++++++++++++++ benchmarks/index.js | 2 + benchmarks/iterate.js | 147 +++++++++++++++++++++++++++++++++++++ benchmarks/iterate.plot.js | 55 ++++++++++++++ 5 files changed, 381 insertions(+) create mode 100644 benchmarks/clear.js create mode 100644 benchmarks/clear.plot.js create mode 100644 benchmarks/iterate.js create mode 100644 benchmarks/iterate.plot.js diff --git a/benchmarks/clear.js b/benchmarks/clear.js new file mode 100644 index 0000000..4608dec --- /dev/null +++ b/benchmarks/clear.js @@ -0,0 +1,122 @@ +'use strict' + +const keyspace = require('keyspace') +const window = 1000 +const progressWindow = window * 100 + +exports.defaults = { + benchmark: { + n: 1e6, + valueSize: 100, + keys: 'seq', + values: 'random', + seed: 'seed' + } +} + +exports.plot = require('./clear.plot') + +exports.run = function (factory, stream, options) { + if (options.n < window) { + throw new RangeError('The "n" option must be >= ' + window) + } else if (options.n % window !== 0) { + throw new Error('The "n" option must be a multiple of ' + window) + } + + const generator = keyspace(options.n, options) + + stream.write('Elapsed (ms), Entries, Bytes, SMA ns/write, CMA entries/s\n') + + function start (db) { + const startTime = Date.now() + const inProgress = 0 // TODO: remove + const totalBytes = 0 // TODO: remove. Can't know + + let totalDeletes = 0 + let timesAccum = 0 + let elapsed + + function report () { + console.log( + 'Cleared', options.n, 'entries in', + Math.floor((Date.now() - startTime) / 1e3) + 's' + ) + + stream.end() + + const it = db.iterator() + + it.next(function (err, key) { + if (err) throw err + if (key !== undefined) throw new Error('Did not clear all') + + it.end(function (err) { + if (err) throw err + + db.close(function (err) { + if (err) throw err + }) + }) + }) + } + + function clear () { + if (totalDeletes >= options.n) return report(Date.now() - startTime) + + const start = process.hrtime() + db.clear({ gte: generator.key(totalDeletes), limit: window }, function (err) { + if (err) throw err + + const duration = process.hrtime(start) + const nano = (duration[0] * 1e9) + duration[1] + + timesAccum += nano / window + totalDeletes += window + + if (totalDeletes % progressWindow === 0) { + console.log('' + inProgress, totalDeletes, + Math.round(totalDeletes / options.n * 100) + '%') + } + + elapsed = Date.now() - startTime + stream.write( + elapsed + + ',' + totalDeletes + + ',' + totalBytes + + ',' + (timesAccum / window).toFixed(3) + + ',' + ((totalDeletes) / (elapsed / 1e3)).toFixed(3) + + '\n') + timesAccum = 0 + clear() + }) + } + + clear() + } + + factory(function (err, db) { + if (err) throw err + + let entries = 0 + + function loop (err) { + if (err) throw err + + console.log('Prep: wrote %d of %d entries', entries, options.n) + if (entries >= options.n) return setTimeout(() => start(db), 500) + + const batch = db.batch() + + for (let i = 0; i < 1e3 && entries < options.n; i++) { + const key = generator.key(entries++) + const value = generator.value() + + batch.put(key, value) + } + + batch.write(loop) + } + + loop() + }) +} diff --git a/benchmarks/clear.plot.js b/benchmarks/clear.plot.js new file mode 100644 index 0000000..9453111 --- /dev/null +++ b/benchmarks/clear.plot.js @@ -0,0 +1,55 @@ +'use strict' + +const e = require('../lib/escape-gnuplot-string') + +module.exports = function (title, description, results) { + const durations = results.map(function (res, i) { + const file = res.csvFile + const title = res.id(results) + + return `'${e(file)}' using ($1/1000):($4) title '${e(title)}' ls ${i + 1} axes x1y1` + }) + + const throughputs = results.map(function (res, i) { + const file = res.csvFile + const title = res.id(results) + + return `'${e(file)}' using ($1/1000):($5) w lines title '${e(title)}' ls ${i + 1} axes x1y1` + }) + + return ` + reset + set terminal pngcairo truecolor enhanced font "Ubuntu Mono,10" size 1920, 1080 background rgb "#1b1b1b" + set datafile separator ',' + + set autoscale y + set ytics mirror + set tics in + set xlabel "Time (seconds)" tc rgb "#999999" + + set key outside tc rgb "#999999" + set border lc rgb "#999999" + + # To plot more than 5 files, add more line styles + set style line 1 lt 7 ps 0.8 lc rgb "#00FFFF" + set style line 2 lt 7 ps 0.8 lc rgb "#D84797" + set style line 3 lt 7 ps 0.8 lc rgb "#23CE6B" + set style line 4 lt 7 ps 0.8 lc rgb "#F5B700" + set style line 5 lt 7 ps 0.8 lc rgb "#731DD8" + + set multiplot layout 2,1 + set lmargin at screen 0.1 + + set title '${e(title)}' tc rgb "#cccccc" offset 0,0.1 font "Ubuntu Mono,12" + set label 1 '${e(description)}' tc rgb "#999999" at graph 0.5,1.10 center front + set ylabel 'SMA ns/read' tc rgb "#999999" + set logscale y + plot ${durations.join(', ')} + + set title "" + set label 1 "" + set ylabel 'CMA Throughput entries/s' tc rgb "#999999" + set nologscale y + plot ${throughputs.join(', ')} + unset multiplot` +} diff --git a/benchmarks/index.js b/benchmarks/index.js index 0d1395c..455eaaa 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -2,4 +2,6 @@ exports.put = require('./put') exports['batch-put'] = require('./batch-put') +exports.iterate = require('./iterate') +exports.clear = require('./clear') exports['self-distribution'] = require('./self-distribution') diff --git a/benchmarks/iterate.js b/benchmarks/iterate.js new file mode 100644 index 0000000..e98c8ca --- /dev/null +++ b/benchmarks/iterate.js @@ -0,0 +1,147 @@ +'use strict' + +const keyspace = require('keyspace') +const ldu = require('../lib/level-du') +const window = 1000 +const progressWindow = window * 100 + +exports.defaults = { + benchmark: { + n: 1e6, + concurrency: 1, + valueSize: 100, + buffers: false, + keys: 'random', + values: 'random', + seed: 'seed' + } +} + +exports.plot = require('./iterate.plot') + +exports.run = function (factory, stream, options) { + if (options.n < window) { + throw new RangeError('The "n" option must be >= ' + window) + } else if (options.n % window !== 0) { + throw new Error('The "n" option must be a multiple of ' + window) + } + + const generator = keyspace(options.n, options) + + stream.write('Elapsed (ms), Entries, Bytes, ns/read, CMA MB/s\n') + + function start (db) { + const startTime = Date.now() + + let inProgress = 0 + let totalReads = 0 + let totalBytes = 0 + let timesAccum = 0 + let elapsed + + function report () { + console.log( + 'Iterated', options.n, 'entries in', + Math.floor((Date.now() - startTime) / 1e3) + 's,', + (Math.floor((totalBytes / 1048576) * 100) / 100) + 'MB' + ) + + stream.end() + + db.close(function (err) { + if (err) throw err + + ldu(db, function (err, size) { + if (err) throw err + if (size) console.log('Database size:', Math.floor(size / 1024 / 1024) + 'M') + }) + }) + } + + function iterate () { + if (totalReads >= options.n) return report(Date.now() - startTime) + if (inProgress >= options.concurrency) return + + inProgress++ + + const it = db.iterator({ + keyAsBuffer: options.buffers, + valueAsBuffer: options.buffers + }) + + function loop () { + if (totalReads >= options.n) return end() + const start = process.hrtime() + + it.next(function (err, key, value) { + if (err) throw err + if (key === undefined && value === undefined) return end() + + const duration = process.hrtime(start) + const nano = (duration[0] * 1e9) + duration[1] + + timesAccum += nano + totalBytes += Buffer.byteLength(key) + Buffer.byteLength(value) + totalReads++ + + if (totalReads % progressWindow === 0) { + console.log('' + inProgress, totalReads, + Math.round(totalReads / options.n * 100) + '%') + } + + if (totalReads % window === 0) { + elapsed = Date.now() - startTime + stream.write( + elapsed + + ',' + totalReads + + ',' + totalBytes + + ',' + (timesAccum / window).toFixed(3) + + ',' + ((totalBytes / 1048576) / (elapsed / 1e3)).toFixed(3) + + '\n') + timesAccum = 0 + } + + loop() + }) + } + + function end () { + it.end(function (err) { + if (err) throw err + inProgress-- + process.nextTick(iterate) + }) + } + + loop() + } + + for (let i = 0; i < options.concurrency; i++) iterate() + } + + factory(function (err, db) { + if (err) throw err + + let entries = 0 + + function loop (err) { + if (err) throw err + + console.log('Prep: wrote %d of %d entries', entries, options.n) + if (entries >= options.n) return setTimeout(() => start(db), 500) + + const batch = db.batch() + + for (let i = 0; i < 1e3 && entries < options.n; i++) { + const key = generator.key(entries++) + const value = generator.value() + + batch.put(key, value) + } + + batch.write(loop) + } + + loop() + }) +} diff --git a/benchmarks/iterate.plot.js b/benchmarks/iterate.plot.js new file mode 100644 index 0000000..95405fb --- /dev/null +++ b/benchmarks/iterate.plot.js @@ -0,0 +1,55 @@ +'use strict' + +const e = require('../lib/escape-gnuplot-string') + +module.exports = function (title, description, results) { + const durations = results.map(function (res, i) { + const file = res.csvFile + const title = res.id(results) + + return `'${e(file)}' using ($1/1000):($4) title '${e(title)}' ls ${i + 1} axes x1y1` + }) + + const throughputs = results.map(function (res, i) { + const file = res.csvFile + const title = res.id(results) + + return `'${e(file)}' using ($1/1000):($5) w lines title '${e(title)}' ls ${i + 1} axes x1y1` + }) + + return ` + reset + set terminal pngcairo truecolor enhanced font "Ubuntu Mono,10" size 1920, 1080 background rgb "#1b1b1b" + set datafile separator ',' + + set autoscale y + set ytics mirror + set tics in + set xlabel "Time (seconds)" tc rgb "#999999" + + set key outside tc rgb "#999999" + set border lc rgb "#999999" + + # To plot more than 5 files, add more line styles + set style line 1 lt 7 ps 0.8 lc rgb "#00FFFF" + set style line 2 lt 7 ps 0.8 lc rgb "#D84797" + set style line 3 lt 7 ps 0.8 lc rgb "#23CE6B" + set style line 4 lt 7 ps 0.8 lc rgb "#F5B700" + set style line 5 lt 7 ps 0.8 lc rgb "#731DD8" + + set multiplot layout 2,1 + set lmargin at screen 0.1 + + set title '${e(title)}' tc rgb "#cccccc" offset 0,0.1 font "Ubuntu Mono,12" + set label 1 '${e(description)}' tc rgb "#999999" at graph 0.5,1.10 center front + set ylabel 'ns/read' tc rgb "#999999" + set logscale y + plot ${durations.join(', ')} + + set title "" + set label 1 "" + set ylabel 'CMA Throughput MB/s' tc rgb "#999999" + set nologscale y + plot ${throughputs.join(', ')} + unset multiplot` +}