mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -05:00
1ba88a7892
Fixes https://github.com/denoland/deno/issues/24323 - Use a Buffer pool for `fromString` - Implement fast call base64 writes - Direct from string `create` method for each encoding op ``` $ deno bench -A bench.mjs # 1.45.1+fee4d3a cpu: Apple M1 Pro runtime: deno 1.45.1+fee4d3a (aarch64-apple-darwin) benchmark time (avg) (min … max) p75 p99 p999 ----------------------------------------------------------- ----------------------------- Buffer.from base64 550 ns/iter (490 ns … 1'265 ns) 572 ns 606 ns 1'265 ns Buffer#write base64 285 ns/iter (259 ns … 371 ns) 307 ns 347 ns 360 ns $ ~/gh/deno/target/release/deno bench -A bench.mjs # this PR cpu: Apple M1 Pro runtime: deno dev (aarch64-apple-darwin) benchmark time (avg) (min … max) p75 p99 p999 ----------------------------------------------------------- ----------------------------- Buffer.from base64 151 ns/iter (145 ns … 770 ns) 148 ns 184 ns 648 ns Buffer#write base64 62.58 ns/iter (60.79 ns … 157 ns) 61.65 ns 75.79 ns 141 ns $ node bench.mjs # v22.4.0 cpu: Apple M1 Pro runtime: node v22.4.0 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p999 ----------------------------------------------------------- ----------------------------- Buffer.from base64 163 ns/iter (96.92 ns … 375 ns) 99.45 ns 127 ns 220 ns Buffer#write base64 75.48 ns/iter (74.97 ns … 134 ns) 75.17 ns 81.83 ns 96.84 ns ```
129 lines
4 KiB
TypeScript
129 lines
4 KiB
TypeScript
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
// TODO(petamoriken): enable prefer-primordials for node polyfills
|
|
// deno-lint-ignore-file prefer-primordials
|
|
|
|
import {
|
|
forgivingBase64Decode,
|
|
forgivingBase64UrlDecode,
|
|
} from "ext:deno_web/00_infra.js";
|
|
import { op_base64_write } from "ext:core/ops";
|
|
|
|
export function asciiToBytes(str: string) {
|
|
const length = str.length;
|
|
const byteArray = new Uint8Array(length);
|
|
for (let i = 0; i < length; ++i) {
|
|
byteArray[i] = str.charCodeAt(i) & 255;
|
|
}
|
|
return byteArray;
|
|
}
|
|
|
|
export function base64ToBytes(str: string) {
|
|
try {
|
|
return forgivingBase64Decode(str);
|
|
} catch {
|
|
str = base64clean(str);
|
|
str = str.replaceAll("-", "+").replaceAll("_", "/");
|
|
return forgivingBase64Decode(str);
|
|
}
|
|
}
|
|
|
|
export function base64Write(
|
|
str: string,
|
|
buffer: Uint8Array,
|
|
offset: number = 0,
|
|
length?: number,
|
|
): number {
|
|
length = length ?? buffer.byteLength - offset;
|
|
try {
|
|
return op_base64_write(str, buffer, offset, length);
|
|
} catch {
|
|
str = base64clean(str);
|
|
str = str.replaceAll("-", "+").replaceAll("_", "/");
|
|
return op_base64_write(str, buffer, offset, length);
|
|
}
|
|
}
|
|
|
|
const INVALID_BASE64_RE = /[^+/0-9A-Za-z-_]/g;
|
|
function base64clean(str: string) {
|
|
// Node takes equal signs as end of the Base64 encoding
|
|
const eqIndex = str.indexOf("=");
|
|
str = eqIndex !== -1 ? str.substring(0, eqIndex).trimStart() : str.trim();
|
|
// Node strips out invalid characters like \n and \t from the string, std/base64 does not
|
|
str = str.replace(INVALID_BASE64_RE, "");
|
|
// Node converts strings with length < 2 to ''
|
|
const length = str.length;
|
|
if (length < 2) return "";
|
|
// Node allows for non-padded base64 strings (missing trailing ===), std/base64 does not
|
|
switch (length % 4) {
|
|
case 0:
|
|
return str;
|
|
case 1:
|
|
return `${str}===`;
|
|
case 2:
|
|
return `${str}==`;
|
|
case 3:
|
|
return `${str}=`;
|
|
default:
|
|
throw new Error("Unexpected NaN value for string length");
|
|
}
|
|
}
|
|
|
|
export function base64UrlToBytes(str: string) {
|
|
str = base64clean(str);
|
|
str = str.replaceAll("+", "-").replaceAll("/", "_");
|
|
return forgivingBase64UrlDecode(str);
|
|
}
|
|
|
|
export function hexToBytes(str: string) {
|
|
const length = str.length >>> 1;
|
|
const byteArray = new Uint8Array(length);
|
|
let i: number;
|
|
for (i = 0; i < length; i++) {
|
|
const a = Number.parseInt(str[i * 2], 16);
|
|
const b = Number.parseInt(str[i * 2 + 1], 16);
|
|
if (Number.isNaN(a) && Number.isNaN(b)) {
|
|
break;
|
|
}
|
|
byteArray[i] = (a << 4) | b;
|
|
}
|
|
// Returning a buffer subarray is okay: This API's return value
|
|
// is never exposed to users and is only ever used for its length
|
|
// and the data within the subarray.
|
|
return i === length ? byteArray : byteArray.subarray(0, i);
|
|
}
|
|
|
|
export function utf16leToBytes(str: string, units?: number) {
|
|
// If units is defined, round it to even values for 16 byte "steps"
|
|
// and use it as an upper bound value for our string byte array's length.
|
|
const length = Math.min(str.length * 2, units ? (units >>> 1) * 2 : Infinity);
|
|
const byteArray = new Uint8Array(length);
|
|
const view = new DataView(byteArray.buffer);
|
|
let i: number;
|
|
for (i = 0; i * 2 < length; i++) {
|
|
view.setUint16(i * 2, str.charCodeAt(i), true);
|
|
}
|
|
// Returning a buffer subarray is okay: This API's return value
|
|
// is never exposed to users and is only ever used for its length
|
|
// and the data within the subarray.
|
|
return i * 2 === length ? byteArray : byteArray.subarray(0, i * 2);
|
|
}
|
|
|
|
export function bytesToAscii(bytes: Uint8Array) {
|
|
let res = "";
|
|
const length = bytes.byteLength;
|
|
for (let i = 0; i < length; ++i) {
|
|
res = `${res}${String.fromCharCode(bytes[i] & 127)}`;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
export function bytesToUtf16le(bytes: Uint8Array) {
|
|
let res = "";
|
|
const length = bytes.byteLength;
|
|
const view = new DataView(bytes.buffer, bytes.byteOffset, length);
|
|
for (let i = 0; i < length - 1; i += 2) {
|
|
res = `${res}${String.fromCharCode(view.getUint16(i, true))}`;
|
|
}
|
|
return res;
|
|
}
|