Skip to content

Commit

Permalink
Improve performance (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
H4ad authored Nov 8, 2023
1 parent 9271982 commit 473fe2f
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 5 deletions.
49 changes: 49 additions & 0 deletions benchmark.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint no-undef: "off" */

import {Buffer} from 'node:buffer';
import {randomBytes} from 'node:crypto';
import benchmark from 'benchmark';
import {
base64ToString,
concatUint8Arrays,
hexToUint8Array,
isUint8Array,
stringToBase64,
stringToUint8Array,
uint8ArrayToBase64,
uint8ArrayToHex,
uint8ArrayToString,
} from './index.js';

const oneMb = 1024 * 1024;
const largeUint8Array = new Uint8Array(randomBytes(oneMb).buffer);
const textFromUint8Array = uint8ArrayToString(largeUint8Array);
const base64FromUint8Array = Buffer.from(textFromUint8Array).toString('base64');
const hexFromUint8Array = uint8ArrayToHex(largeUint8Array);

const suite = new benchmark.Suite();

suite.add('isUint8Array', () => isUint8Array(largeUint8Array));

suite.add('concatUint8Arrays with 2 arrays', () => concatUint8Arrays([largeUint8Array, largeUint8Array]));

suite.add('concatUint8Arrays with 3 arrays', () => concatUint8Arrays([largeUint8Array, largeUint8Array]));

suite.add('concatUint8Arrays with 4 arrays', () => concatUint8Arrays([largeUint8Array, largeUint8Array, largeUint8Array, largeUint8Array]));

suite.add('uint8ArrayToString', () => uint8ArrayToString(largeUint8Array));

suite.add('stringToUint8Array', () => stringToUint8Array(textFromUint8Array));

suite.add('uint8ArrayToBase64', () => uint8ArrayToBase64(largeUint8Array));

suite.add('stringToBase64', () => stringToBase64(textFromUint8Array));

suite.add('base64ToString', () => base64ToString(base64FromUint8Array));

suite.add('uint8ArrayToHex', () => uint8ArrayToHex(largeUint8Array));

suite.add('hexToUint8Array', () => hexToUint8Array(hexFromUint8Array));

suite.on('cycle', event => console.log(event.target.toString()));
suite.run({async: false});
35 changes: 31 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
const objectToString = Object.prototype.toString;
const uint8ArrayStringified = '[object Uint8Array]';

export function isUint8Array(value) {
return value && objectToString.call(value) === '[object Uint8Array]';
if (!value) {
return false;
}

if (value.constructor === Uint8Array) {
return true;
}

return objectToString.call(value) === uint8ArrayStringified;
}

export function assertUint8Array(value) {
Expand Down Expand Up @@ -92,9 +101,11 @@ export function compareUint8Arrays(a, b) {
return 0;
}

const cachedDecoder = new globalThis.TextDecoder();

export function uint8ArrayToString(array) {
assertUint8Array(array);
return (new globalThis.TextDecoder()).decode(array);
return cachedDecoder.decode(array);
}

function assertString(value) {
Expand All @@ -103,9 +114,11 @@ function assertString(value) {
}
}

const cachedEncoder = new globalThis.TextEncoder();

export function stringToUint8Array(string) {
assertString(string);
return (new globalThis.TextEncoder()).encode(string);
return cachedEncoder.encode(string);
}

function base64ToBase64Url(base64) {
Expand All @@ -116,11 +129,25 @@ function base64UrlToBase64(base64url) {
return base64url.replaceAll('-', '+').replaceAll('_', '/');
}

// Reference: https://phuoc.ng/collection/this-vs-that/concat-vs-push/
const MAX_BLOCK_SIZE = 65_535;

export function uint8ArrayToBase64(array, {urlSafe = false} = {}) {
assertUint8Array(array);

let base64;

if (array.length < MAX_BLOCK_SIZE) {
// Required as `btoa` and `atob` don't properly support Unicode: https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem
const base64 = globalThis.btoa(String.fromCodePoint(...array));
base64 = globalThis.btoa(String.fromCodePoint.apply(this, array));
} else {
base64 = '';
for (const value of array) {
base64 += String.fromCodePoint(value);
}

base64 = globalThis.btoa(base64);
}

return urlSafe ? base64ToBase64Url(base64) : base64;
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"devDependencies": {
"ava": "^5.3.1",
"typescript": "^5.2.2",
"xo": "^0.56.0"
"xo": "^0.56.0",
"benchmark": "2.1.4"
}
}
6 changes: 6 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ test('uint8ArrayToBase64 and base64ToUint8Array', t => {
t.deepEqual(base64ToUint8Array(base64), fixture);
});

test('should handle uint8ArrayToBase64 with 200k items', t => {
const fixture = stringToUint8Array('H'.repeat(200_000));
const base64 = uint8ArrayToBase64(fixture);
t.deepEqual(base64ToUint8Array(base64), fixture);
});

test('uint8ArrayToBase64 and base64ToUint8Array #2', t => {
const fixture = stringToUint8Array('a Ā 𐀀 文 🦄');
t.deepEqual(base64ToUint8Array(uint8ArrayToBase64(base64ToUint8Array(uint8ArrayToBase64(fixture)))), fixture);
Expand Down

0 comments on commit 473fe2f

Please sign in to comment.