endojs / endo

Endo is a distributed secure JavaScript sandbox, based on SES
Apache License 2.0
804 stars 71 forks source link

perf(syrup): Minimize expensive operations when encoding #2000

Closed gibson042 closed 7 months ago

gibson042 commented 8 months ago

refs: #1984

Description

encodeString:

encodeArray:

encodeAny:

Effect ```shell esbench -n 100 --repetitions 10 --eshost-option '-h V8,*XS' --eshost-option '-m' \ --init-file <(printf '%s\n' ' import { smuggle } from "data:text/javascript,delete globalThis.harden; if(typeof TextEncoder === `undefined`) Object.defineProperties(globalThis, { TextDecoder: { writable: true, enumerable: false, configurable: true, value: class TextDecoder {} }, TextEncoder: { writable: true, enumerable: false, configurable: true, value: class TextEncoder { encodeInto(str, buf){ const bufLen = buf.length; let read = 0, written = 0; if (bufLen > 0) for(const ch of str){ const cp = ch.codePointAt(0), n = cp >= 0x10000 ? 4 : cp >= 0x800 ? 3 : cp >= 0x80 ? 2 : 1; if(written + n > bufLen) break; read += ch.length; written += n; if(written === bufLen) break; } return { read, written }; } } }, }); globalThis.console ||= Object.fromEntries(`debug log info warn error groupCollapsed groupEnd`.split(` `).map(m => [m, print])); /* abuse console.clear to smuggle values */ let smuggled; export const smuggle = val => { smuggled = val; }; { globalThis.process ||= { env: {} }; process.env.LOCKDOWN_CONSOLE_TAMING = `unsafe`; console.clear = () => smuggled; }"; import "@endo/init"; import { encodeSyrup } from "@endo/syrup"; smuggle({ encodeSyrup }); ' | \ npx rollup@4.9.5 -p @rollup/plugin-node-resolve -p ~/lib/rollup-plugin-js-data-url.js -f iife | \ npx terser@v5.26.0 -cm --toplevel ) \ ' const { encodeSyrup } = console.clear(), capacity = 1; const str = "A".repeat(42), short = Array(5).fill(str), mid = Array(100).fill(str), long = Array(1000).fill(str); ' '{ encodeShort: `result = encodeSyrup(short, { length: capacity });`, encodeMid: `result = encodeSyrup(mid, { length: capacity });`, encodeLong: `result = encodeSyrup(long, { length: capacity });`, }' ``` Before: ```console #### Moddable XS encodeShort: 2.08 ops/ms encodeMid: 0.22 ops/ms encodeLong: 0.02 ops/ms #### V8 encodeShort: 8.33 ops/ms encodeMid: 1.14 ops/ms encodeLong: 0.12 ops/ms ``` After: ```console #### Moddable XS encodeShort: 3.03 ops/ms encodeMid: 0.23 ops/ms encodeLong: 0.02 ops/ms #### V8 encodeShort: 10.00 ops/ms encodeMid: 1.35 ops/ms encodeLong: 0.16 ops/ms ```

Security Considerations

None known.

Scaling Considerations

For large strings, performance is still dominated by currently-unavoidable O(n) operations.

Documentation Considerations

n/a

Testing Considerations

n/a

Upgrade Considerations

n/a

kriskowal commented 7 months ago

(I don’t advise spending a great deal of time on @endo/syrup. I suspect that the next run at Syrup, if one should occur, is to turn it into a streaming codec to avoid allocating an intermediate representation.)