Skip to content

Commit

Permalink
chore(base64): Improve benchmark script (merge #1986)
Browse files Browse the repository at this point in the history
  • Loading branch information
gibson042 authored Jan 22, 2024
2 parents bc47a24 + c695bf8 commit a22c936
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 45 deletions.
5 changes: 4 additions & 1 deletion packages/base64/src/decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import { monodu64, padding } from './common.js';
* arranges a native binding on the global object.
* We use that if it is available instead.
*
* This function is exported from this *file* for use in benchmarking,
* but is not part of the *module*'s public API.
*
* @param {string} string Base64-encoded string
* @param {string} [name] The name of the string as it will appear in error
* messages.
* @returns {Uint8Array} decoded bytes
*/
const jsDecodeBase64 = (string, name = '<unknown>') => {
export const jsDecodeBase64 = (string, name = '<unknown>') => {
const data = new Uint8Array(Math.ceil((string.length * 4) / 3));
let register = 0;
let quantum = 0;
Expand Down
5 changes: 4 additions & 1 deletion packages/base64/src/encode.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import { alphabet64, padding } from './common.js';
* arranges a native binding on the global object.
* We use that if it is available instead.
*
* This function is exported from this *file* for use in benchmarking,
* but is not part of the *module*'s public API.
*
* @param {Uint8Array} data
* @returns {string} base64 encoding
*/
const jsEncodeBase64 = data => {
export const jsEncodeBase64 = data => {
// A cursory benchmark shows that string concatenation is about 25% faster
// than building an array and joining it in v8, in 2020, for strings of about
// 100 long.
Expand Down
125 changes: 82 additions & 43 deletions packages/base64/test/bench-main.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,94 @@
/* eslint-disable no-restricted-globals */
/* global print */

// This is a quick and dirty benchmark to encode and decode base64, intended to
// be run manually to measure performance progress or regressions.
// This is a hand-rolled benchmark that attempts as much as possible to avoid
// incorporating the noise of function calls and the underlying Date.now()
// syscall, by exponentially probing for the number of operations that can be
// executed within each benchmark's deadline.
// incorporating the noise of function calls and the underlying
// performance.now()/Date.now() syscall, by exponentially probing for the number
// of operations that can be executed within each benchmark's deadline.

import { encodeBase64, decodeBase64 } from '../index.js';
import { jsEncodeBase64 } from '../src/encode.js';
import { jsDecodeBase64 } from '../src/decode.js';

async function main() {
const log = (typeof console !== 'undefined' && console.log) || print;
/** @type {typeof Date.now} */
const now = await (async () => {
try {
const { performance } = await import('perf_hooks');
if (performance.now) {
return performance.now.bind(performance);
}
} catch (_err) {
// eslint-disable-next-line no-empty
}
return Date.now;
})();

const timeout = 1000; // ms

const shortString =
'there once a rich man from nottingham who tried to cross the river. what a dope, he tripped on a rope. now look at him shiver.';
const string = new Array(10000).fill(shortString).join(' ');
const shortData = encodeBase64(shortString);
const data = encodeBase64(string);

const timeout = 1000; // ms

const string = new Array(10000)
.fill(
'there once a rich man from nottingham who tried to cross the river. what a dope, he tripped on a rope. now look at him shiver.',
)
.join(' ');
const data = encodeBase64(string);

{
const start = Date.now();
const deadline = start + timeout / 2;
let operations = 0;
for (let n = 1; Date.now() < deadline; n *= 2) {
for (let i = 0; i < n; i += 1) {
encodeBase64(string);
/** @type {string} */
let result;

for (const [label, fn, input] of [
['encodes', encodeBase64, string],
['JS short-string encodes', jsEncodeBase64, shortString],
]) {
for (const pass of [1, 2]) {
const start = now();
const deadline = start + timeout / 2;
let operations = 0;
for (let n = 1; now() < deadline; n *= 2) {
for (let i = 0; i < n; i += 1) {
result = fn(input);
}
operations += n;
}
const end = now();
const duration = end - start;
log(
`[pass ${pass}] ${label}`,
(operations * input.length) / duration,
'characters per millisecond',
);
}
operations += n;
}
const end = Date.now();
const duration = end - start;
console.log(
'encodes',
(operations * string.length) / duration,
'characters per millisecond',
);
}

{
const start = Date.now();
const deadline = start + timeout / 2;
let operations = 0;
for (let n = 1; Date.now() < deadline; n *= 2) {
for (let i = 0; i < n; i += 1) {
decodeBase64(data);
if (result.length < 100) throw Error(`unexpected result: ${result}`);

for (const [label, fn, input] of [
['decodes', decodeBase64, data],
['JS short-string decodes', jsDecodeBase64, shortData],
]) {
for (const pass of [1, 2]) {
const start = now();
const deadline = start + timeout / 2;
let operations = 0;
for (let n = 1; now() < deadline; n *= 2) {
for (let i = 0; i < n; i += 1) {
result = fn(input);
}
operations += n;
}
const end = now();
const duration = end - start;
log(
`[pass ${pass}] ${label}`,
(operations * input.length) / duration,
'bytes per millisecond',
);
}
operations += n;
}
const end = Date.now();
const duration = end - start;
console.log(
'decodes',
(operations * data.length) / duration,
'bytes per millisecond',
);

if (result.length < 100) throw Error(`unexpected result: ${result}`);
}

main();

0 comments on commit a22c936

Please sign in to comment.