Skip to content

Instantly share code, notes, and snippets.

@ChALkeR
Last active November 5, 2025 02:22
Show Gist options
  • Select an option

  • Save ChALkeR/f49edd556ff3d08638eb3e81b43ce178 to your computer and use it in GitHub Desktop.

Select an option

Save ChALkeR/f49edd556ff3d08638eb3e81b43ce178 to your computer and use it in GitHub Desktop.
// 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