Skip to content

Instantly share code, notes, and snippets.

@peter-leonov
Created July 26, 2020 13:45
Show Gist options
  • Save peter-leonov/8ad54c27e1924a556d6c1c11345011e1 to your computer and use it in GitHub Desktop.
Save peter-leonov/8ad54c27e1924a556d6c1c11345011e1 to your computer and use it in GitHub Desktop.
Simple not-byte-precise base64 encoder
const CODE_A = "A".charCodeAt(0);
const CODE_a = "a".charCodeAt(0);
const CODE_0 = "0".charCodeAt(0);
const CODE_p = "+".charCodeAt(0);
const CODE_s = "/".charCodeAt(0);
// Dear v8, inline this please
const toA = (n) =>
n === 63
? CODE_s
: n === 62
? CODE_p
: n >= 52
? n - 52 + CODE_0
: n >= 26
? n - 26 + CODE_a
: n + CODE_A;
const SRC_BLOCK_SIZE = 3; // bytes
const DST_BLOCK_SIZE = 4; // bytes
function encode(bytes) {
const blocks = (bytes.length / SRC_BLOCK_SIZE) | 0;
const blockBytes = blocks * SRC_BLOCK_SIZE;
const left = bytes.length - blockBytes;
const resultSize = blocks * DST_BLOCK_SIZE + (left ? left + 1 : 0);
const res = new Uint8Array(resultSize);
let isrc = 0;
let idst = 0;
for (
;
isrc < blockBytes;
isrc += SRC_BLOCK_SIZE, idst += DST_BLOCK_SIZE
) {
const s1 = bytes[isrc + 0];
const s2 = bytes[isrc + 1];
const s3 = bytes[isrc + 2];
res[idst + 0] = toA(s1 >> 2);
res[idst + 1] = toA(((s1 & 0b00000011) << 4) | (s2 >> 4));
res[idst + 2] = toA(((s2 & 0b00001111) << 2) | (s3 >> 6));
res[idst + 3] = toA(s3 & 0b111111);
}
switch (left) {
case 1:
{
const s1 = bytes[isrc];
res[idst + 0] = toA(s1 >> 2);
res[idst + 1] = toA((s1 & 0b00000011) << 4);
}
break;
case 2:
{
const s1 = bytes[isrc];
const s2 = bytes[isrc + 1];
res[idst + 0] = toA(s1 >> 2);
res[idst + 1] = toA(((s1 & 0b00000011) << 4) | (s2 >> 4));
res[idst + 2] = toA((s2 & 0b00001111) << 2);
}
break;
}
return res;
}
const toBitString = (p) => (n) => n.toString(2).padStart(p, "0");
// const bytes = new TextEncoder().encode("abcdefghi0");
// console.log(Array.from(bytes).map(toBitString(8)).join(""));
// console.log(Array.from(map8to6(bytes)).map(toBitString(6)).join(""));
// console.log(
// Array.from(encode(bytes))
// .map((c) => String.fromCharCode(c))
// .join("")
// );
// average full res photo
const size = 2_000_000;
const data = new Uint8Array(size);
let rng = 123;
for (let i = 0; i <= size; i++) {
rng = (rng * 110315 + 1752157) & 0x7fffffff;
data[i] = rng % 256;
}
console.time("warmup");
for (let i = 0; i <= 100; i++) {
encode(data);
}
console.timeEnd("warmup");
// gives 701.285ms on 2012 MacBook Air
console.time("1000x");
for (let i = 0; i <= 1000; i++) {
encode(data);
}
console.timeEnd("1000x");
// gives 6.700s on 2012 MacBook Air
console.log("done");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment