diff options
author | Matt Mastracci <matthew@mastracci.com> | 2023-04-30 10:50:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-30 08:50:24 +0000 |
commit | bb1f5e4262940a966e6314f57a4267514911d262 (patch) | |
tree | 0b5b870e34fca10daf8e664eb4214e5e756daf53 /core/01_core.js | |
parent | 9c8ebce3dcc784f1a6ecd29d5fe0b3d35256ab82 (diff) |
perf(core): async op pseudo-codegen and performance work (#18887)
Performance:
```
async_ops.js: 760k -> 1030k (!)
async_ops_deferred.js: 730k -> 770k
Deno.serve bench: 118k -> 124k
WS test w/ third_party/prebuilt/mac/load_test 100 localhost 8000 0 0: unchanged
Startup time: approx 0.5ms slower (13.7 -> 14.2ms)
```
Diffstat (limited to 'core/01_core.js')
-rw-r--r-- | core/01_core.js | 465 |
1 files changed, 423 insertions, 42 deletions
diff --git a/core/01_core.js b/core/01_core.js index a8bdeb2a8..3972dec33 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -16,11 +16,15 @@ ObjectAssign, ObjectFreeze, ObjectFromEntries, + ObjectKeys, Promise, + PromiseReject, + PromiseResolve, PromisePrototypeThen, RangeError, ReferenceError, ReflectHas, + ReflectApply, SafeArrayIterator, SafeMap, SafePromisePrototypeFinally, @@ -32,7 +36,7 @@ TypeError, URIError, } = window.__bootstrap.primordials; - const { ops } = window.Deno.core; + const { ops, asyncOps } = window.Deno.core; const build = { target: "unknown", @@ -85,6 +89,17 @@ return opCallTracingEnabled; } + function movePromise(promiseId) { + const idx = promiseId % RING_SIZE; + // Move old promise from ring to map + const oldPromise = promiseRing[idx]; + if (oldPromise !== NO_PROMISE) { + const oldPromiseId = promiseId - RING_SIZE; + MapPrototypeSet(promiseMap, oldPromiseId, oldPromise); + } + return promiseRing[idx] = NO_PROMISE; + } + function setPromise(promiseId) { const idx = promiseId % RING_SIZE; // Move old promise from ring to map @@ -208,7 +223,29 @@ return error; } - function unwrapOpResult(res) { + function unwrapOpError(hideFunction) { + return (res) => { + // .$err_class_name is a special key that should only exist on errors + const className = res?.$err_class_name; + if (!className) { + return res; + } + + const errorBuilder = errorMap[className]; + const err = errorBuilder ? errorBuilder(res.message) : new Error( + `Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`, + ); + // Set .code if error was a known OS error, see error_codes.rs + if (res.code) { + err.code = res.code; + } + // Strip unwrapOpResult() and errorBuilder() calls from stack trace + ErrorCaptureStackTrace(err, hideFunction); + throw err; + }; + } + + function unwrapOpResultNewPromise(id, res, hideFunction) { // .$err_class_name is a special key that should only exist on errors if (res?.$err_class_name) { const className = res.$err_class_name; @@ -221,59 +258,359 @@ err.code = res.code; } // Strip unwrapOpResult() and errorBuilder() calls from stack trace - ErrorCaptureStackTrace(err, unwrapOpResult); - throw err; + ErrorCaptureStackTrace(err, hideFunction); + return PromiseReject(err); } - return res; + const promise = PromiseResolve(res); + promise[promiseIdSymbol] = id; + return promise; + } + + /* +Basic codegen. + +TODO(mmastrac): automate this (handlebars?) + +let s = ""; +const vars = "abcdefghijklm"; +for (let i = 0; i < 10; i++) { + let args = ""; + for (let j = 0; j < i; j++) { + args += `${vars[j]},`; + } + s += ` + case ${i}: + fn = function async_op_${i}(${args}) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, ${args}); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_${i}); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_${i}); + return PromiseReject(err); + } + let promise = PromisePrototypeThen(setPromise(id), unwrapOpError(eventLoopTick)); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + `; +} + */ + + // This function is called once per async stub + function asyncStub(opName, args) { + setUpAsyncStub(opName); + return ReflectApply(ops[opName], undefined, args); + } + + function setUpAsyncStub(opName) { + const originalOp = asyncOps[opName]; + let fn; + // The body of this switch statement can be generated using the script above. + switch (originalOp.length - 1) { + case 0: + fn = function async_op_0() { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_0); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_0); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 1: + fn = function async_op_1(a) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_1); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_1); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 2: + fn = function async_op_2(a, b) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_2); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_2); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 3: + fn = function async_op_3(a, b, c) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_3); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_3); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 4: + fn = function async_op_4(a, b, c, d) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c, d); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_4); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_4); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 5: + fn = function async_op_5(a, b, c, d, e) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c, d, e); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_5); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_5); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 6: + fn = function async_op_6(a, b, c, d, e, f) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c, d, e, f); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_6); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_6); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 7: + fn = function async_op_7(a, b, c, d, e, f, g) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c, d, e, f, g); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_7); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_7); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 8: + fn = function async_op_8(a, b, c, d, e, f, g, h) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c, d, e, f, g, h); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_8); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_8); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + case 9: + fn = function async_op_9(a, b, c, d, e, f, g, h, i) { + const id = nextPromiseId++; + try { + const maybeResult = originalOp(id, a, b, c, d, e, f, g, h, i); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, async_op_9); + } + } catch (err) { + movePromise(id); + ErrorCaptureStackTrace(err, async_op_9); + return PromiseReject(err); + } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); + promise = handleOpCallTracing(opName, id, promise); + promise[promiseIdSymbol] = id; + return promise; + }; + break; + + default: + throw new Error( + `Too many arguments for async op codegen (length of ${opName} was ${ + originalOp.length - 1 + })`, + ); + } + return (ops[opName] = fn); } function opAsync2(name, arg0, arg1) { const id = nextPromiseId++; - let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult); - let maybeResult; try { - maybeResult = ops[name](id, arg0, arg1); + const maybeResult = asyncOps[name](id, arg0, arg1); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, opAsync2); + } } catch (err) { - // Cleanup the just-created promise - getPromise(id); - if (!ReflectHas(ops, name)) { - throw new TypeError(`${name} is not a registered op`); + movePromise(id); + if (!ReflectHas(asyncOps, name)) { + return PromiseReject(new TypeError(`${name} is not a registered op`)); } - // Rethrow the error - throw err; + ErrorCaptureStackTrace(err, opAsync2); + return PromiseReject(err); } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); promise = handleOpCallTracing(name, id, promise); promise[promiseIdSymbol] = id; - if (typeof maybeResult !== "undefined") { - const promise = getPromise(id); - promise.resolve(maybeResult); - } - return promise; } function opAsync(name, ...args) { const id = nextPromiseId++; - let promise = PromisePrototypeThen(setPromise(id), unwrapOpResult); - let maybeResult; try { - maybeResult = ops[name](id, ...new SafeArrayIterator(args)); + const maybeResult = asyncOps[name](id, ...new SafeArrayIterator(args)); + if (maybeResult !== undefined) { + movePromise(id); + return unwrapOpResultNewPromise(id, maybeResult, opAsync); + } } catch (err) { - // Cleanup the just-created promise - getPromise(id); - if (!ReflectHas(ops, name)) { - throw new TypeError(`${name} is not a registered op`); + movePromise(id); + if (!ReflectHas(asyncOps, name)) { + return PromiseReject(new TypeError(`${name} is not a registered op`)); } - // Rethrow the error - throw err; + ErrorCaptureStackTrace(err, opAsync); + return PromiseReject(err); } + let promise = PromisePrototypeThen( + setPromise(id), + unwrapOpError(eventLoopTick), + ); promise = handleOpCallTracing(name, id, promise); promise[promiseIdSymbol] = id; - if (typeof maybeResult !== "undefined") { - const promise = getPromise(id); - promise.resolve(maybeResult); - } - return promise; } @@ -439,8 +776,52 @@ ); } + // Eagerly initialize ops for snapshot purposes + for (const opName of new SafeArrayIterator(ObjectKeys(asyncOps))) { + setUpAsyncStub(opName); + } + + function generateAsyncOpHandler(/* opNames... */) { + const fastOps = {}; + for (const opName of new SafeArrayIterator(arguments)) { + if (ops[opName] === undefined) { + throw new Error(`Unknown or disabled op '${opName}'`); + } + if (asyncOps[opName] !== undefined) { + fastOps[opName] = setUpAsyncStub(opName); + } else { + fastOps[opName] = ops[opName]; + } + } + return fastOps; + } + + const { + op_close: close, + op_try_close: tryClose, + op_read: read, + op_read_all: readAll, + op_write: write, + op_write_all: writeAll, + op_read_sync: readSync, + op_write_sync: writeSync, + op_shutdown: shutdown, + } = generateAsyncOpHandler( + "op_close", + "op_try_close", + "op_read", + "op_read_all", + "op_write", + "op_write_all", + "op_read_sync", + "op_write_sync", + "op_shutdown", + ); + // Extra Deno.core.* exports const core = ObjectAssign(globalThis.Deno.core, { + asyncStub, + generateAsyncOpHandler, opAsync, opAsync2, resources, @@ -460,15 +841,15 @@ unrefOp, setReportExceptionCallback, setPromiseHooks, - close: (rid) => ops.op_close(rid), - tryClose: (rid) => ops.op_try_close(rid), - read: opAsync.bind(null, "op_read"), - readAll: opAsync.bind(null, "op_read_all"), - write: opAsync.bind(null, "op_write"), - writeAll: opAsync.bind(null, "op_write_all"), - readSync: (rid, buffer) => ops.op_read_sync(rid, buffer), - writeSync: (rid, buffer) => ops.op_write_sync(rid, buffer), - shutdown: opAsync.bind(null, "op_shutdown"), + close, + tryClose, + read, + readAll, + write, + writeAll, + readSync, + writeSync, + shutdown, print: (msg, isErr) => ops.op_print(msg, isErr), setMacrotaskCallback, setNextTickCallback, |