Last active
November 5, 2025 02:22
-
-
Save ChALkeR/f49edd556ff3d08638eb3e81b43ce178 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Benchmark impl | |
| const fRps = (rps) => (rps > 10 ? Math.round(rps).toLocaleString() : rps.toPrecision(2)) | |
| const fTime = (ns) => { | |
| const us = ns / 10n ** 3n | |
| if (us < 2n) return `${ns}ns` | |
| const ms = us / 10n ** 3n | |
| if (ms < 2n) return `${us}μs` | |
| const s = ms / 10n ** 3n | |
| if (s < 10n) return `${ms}ms` | |
| const min = s / 60n | |
| return min < 5n ? `${s}s` : `${min}min` | |
| } | |
| const { performance, scheduler, process, requestAnimationFrame, gc } = globalThis | |
| const getTime = (() => { | |
| if (process) return () => process.hrtime.bigint() | |
| if (performance) return () => BigInt(Math.round(performance.now() * 1e6)) | |
| return () => BigInt(Math.round(Date.now() * 1e6)) | |
| })() | |
| async function benchmark(name, options, fn) { | |
| if (typeof options === 'function') [fn, options] = [options, undefined] | |
| if (options?.skip) return | |
| const { args, timeout = 1000 } = options ?? {} | |
| // This will pause us for a bit, but we don't care - having a non-busy process is more important | |
| await new Promise((resolve) => setTimeout(resolve, 0)) | |
| if (requestAnimationFrame) await new Promise((resolve) => requestAnimationFrame(resolve)) | |
| if (scheduler?.yield) await scheduler.yield() | |
| if (gc) for (let i = 0; i < 4; i++) gc() | |
| let min, max | |
| let total = 0n | |
| let count = 0 | |
| while (true) { | |
| const arg = args ? args[count % args.length] : count | |
| count++ | |
| const start = getTime() | |
| const val = fn(arg) | |
| if (val instanceof Promise) await val | |
| const stop = getTime() | |
| const diff = stop - start | |
| total += diff | |
| if (min === undefined || min > diff) min = diff | |
| if (max === undefined || max < diff) max = diff | |
| if (total >= BigInt(timeout) * 10n ** 6n) break | |
| } | |
| const mean = total / BigInt(count) | |
| let res = `${name} x ${fRps(1e9 / Number(mean))} ops/sec @ ${fTime(mean)}/op` | |
| if (fTime(min) !== fTime(max)) res += ` (${fTime(min)}..${fTime(max)})` | |
| console.log(res) | |
| if (gc) for (let i = 0; i < 4; i++) gc() | |
| } | |
| // Actual code | |
| const seed = crypto.getRandomValues(new Uint8Array(5 * 1024)) | |
| const bufsRaw = [] | |
| const N = 3000 | |
| for (let i = 0; i < N; i++) { | |
| bufsRaw.push(seed.subarray(Math.floor(Math.random() * 100)).map((x, j) => x + i * j)) | |
| } | |
| const replacementChar = String.fromCodePoint(0xff_fd) // We don't expect much of these in real usage, and rng will spawn a lot of those, so strip | |
| const strings = bufsRaw.map((x) => Buffer.from(x).toString().replaceAll(replacementChar, '∀')) // loose, but we want that here | |
| const bufs = strings.map((x) => Buffer.from(x)) | |
| const asciiBufs = bufsRaw.map((x) => Buffer.from(x.map((c) => (c >= 0x80 ? c - 0x80 : c)))) | |
| const asciiStrings = asciiBufs.map((x) => x.toString()) | |
| const textDecoder = new TextDecoder('utf8', { fatal: true }) | |
| const textDecoderLoose = new TextDecoder() | |
| const textEncoder = new TextEncoder() | |
| const main = async () => { | |
| // [name, impl] | |
| const utf8toString = [ | |
| ['TextDecoder', (x) => textDecoder.decode(x)], | |
| ['TextDecoder (loose)', (x) => textDecoderLoose.decode(x)], | |
| ['Buffer', (x) => x.toString('utf8')], | |
| ] | |
| // [name, impl] | |
| const utf8fromString = [ | |
| ['TextEncoder', (x) => textEncoder.encode(x)], | |
| ['Buffer', (x) => Buffer.from(x, 'utf8')], | |
| ] | |
| for (const [name, f] of utf8toString) { | |
| await benchmark(`utf8toString, ascii: ${name}`, { args: asciiBufs }, f) | |
| } | |
| console.log() | |
| for (const [name, f] of utf8toString) { | |
| await benchmark(`utf8toString, complex: ${name}`, { args: bufs }, f) | |
| } | |
| console.log() | |
| for (const [name, f] of utf8fromString) { | |
| await benchmark(`utf8fromString, ascii: ${name}`, { args: asciiStrings }, f) | |
| } | |
| console.log() | |
| for (const [name, f] of utf8fromString) { | |
| await benchmark(`utf8fromString, complex: ${name}`, { args: strings }, f) | |
| } | |
| } | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment