Files
dashfront/scripts/bench-avif-encode.mjs
2026-10-04 08:18:36 +02:00

108 lines
3.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Benchmark AVIF encode options (+ JPEG/WebP reference) on a real fixture.
* Usage: node scripts/bench-avif-encode.mjs [path-to-avif]
*/
import { readFile } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import decodeAvif, { init as initAvifDecode } from '@jsquash/avif/decode.js';
import encodeAvif, { init as initAvifEncode } from '@jsquash/avif/encode.js';
import encodeJpeg, { init as initJpegEncode } from '@jsquash/jpeg/encode.js';
import encodeWebp, { init as initWebpEncode } from '@jsquash/webp/encode.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '..');
const RUNS = 3;
const fixturePath = resolve(
process.argv[2] ?? resolve(root, 'tmp/bench-source.avif'),
);
async function compileWasm(relPath) {
const bytes = await readFile(resolve(root, relPath));
return WebAssembly.compile(bytes);
}
function median(values) {
const sorted = [...values].sort((a, b) => a - b);
return sorted[Math.floor(sorted.length / 2)];
}
async function timeEncode(label, fn) {
const times = [];
let bytes = 0;
for (let i = 0; i < RUNS; i += 1) {
const start = performance.now();
const buf = await fn();
times.push(performance.now() - start);
bytes = buf.byteLength ?? buf.length;
}
return {
label,
encodeMs: Math.round(median(times)),
outputBytes: bytes,
};
}
console.log('init wasm…');
await initAvifDecode(await compileWasm('node_modules/@jsquash/avif/codec/dec/avif_dec.wasm'));
await initAvifEncode(await compileWasm('node_modules/@jsquash/avif/codec/enc/avif_enc.wasm'));
await initJpegEncode(await compileWasm('node_modules/@jsquash/jpeg/codec/enc/mozjpeg_enc.wasm'));
await initWebpEncode(await compileWasm('node_modules/@jsquash/webp/codec/enc/webp_enc.wasm'));
const raw = await readFile(fixturePath);
console.log(`fixture: ${fixturePath} (${raw.byteLength} bytes)`);
const decodeStarted = performance.now();
const imageData = await decodeAvif(raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength));
const decodeMs = Math.round(performance.now() - decodeStarted);
console.log(`decode: ${decodeMs}ms → ${imageData.width}×${imageData.height}`);
// Warmup
await encodeAvif(imageData, { quality: 80, speed: 10 });
const variants = [
{ label: 'avif q80/s6 (baseline)', fn: () => encodeAvif(imageData, { quality: 80, speed: 6 }) },
{ label: 'avif q80/s8', fn: () => encodeAvif(imageData, { quality: 80, speed: 8 }) },
{ label: 'avif q80/s10', fn: () => encodeAvif(imageData, { quality: 80, speed: 10 }) },
{ label: 'avif q70/s8', fn: () => encodeAvif(imageData, { quality: 70, speed: 8 }) },
{ label: 'avif q70/s10', fn: () => encodeAvif(imageData, { quality: 70, speed: 10 }) },
{ label: 'jpeg q80 (ref)', fn: () => encodeJpeg(imageData, { quality: 80 }) },
{ label: 'webp q80 (ref)', fn: () => encodeWebp(imageData, { quality: 80 }) },
];
const rows = [];
for (const variant of variants) {
process.stdout.write(`… ${variant.label}\n`);
rows.push(await timeEncode(variant.label, variant.fn));
}
const baseline = rows.find((row) => row.label.includes('baseline'));
console.log('\nresults (median of 3 runs):');
console.log(
'label'.padEnd(28),
'encodeMs'.padStart(10),
'bytes'.padStart(10),
'vsBaseMs'.padStart(10),
'vsBaseSz'.padStart(10),
);
for (const row of rows) {
const vsMs = baseline ? `${(row.encodeMs / baseline.encodeMs).toFixed(2)}x` : '-';
const vsSz = baseline ? `${(row.outputBytes / baseline.outputBytes).toFixed(2)}x` : '-';
console.log(
row.label.padEnd(28),
String(row.encodeMs).padStart(10),
String(row.outputBytes).padStart(10),
vsMs.padStart(10),
vsSz.padStart(10),
);
}
console.log(JSON.stringify({
fixturePath,
decodeMs,
size: `${imageData.width}×${imageData.height}`,
rows,
}, null, 2));