From 1abc19fd40e906755037c39edb14594965f1c6b7 Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Wed, 1 Feb 2023 20:33:14 +0100 Subject: [PATCH] perf(core): speed up promise hook dispatch --- core/01_core.js | 120 +++++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 43 deletions(-) diff --git a/core/01_core.js b/core/01_core.js index d3cd1d7bea79cb..7e7d17c88b0114 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -17,6 +17,7 @@ ArrayPrototypeMap, ErrorCaptureStackTrace, Function, + FunctionPrototypeToString, Promise, ObjectAssign, ObjectFromEntries, @@ -28,6 +29,7 @@ MapPrototypeSet, PromisePrototypeThen, ReflectApply, + RegExpPrototypeTest, SafePromisePrototypeFinally, StringPrototypeSlice, SymbolFor, @@ -326,43 +328,84 @@ } const InterruptedPrototype = Interrupted.prototype; - const promiseHooks = { - init: [], - before: [], - after: [], - resolve: [], - hasBeenSet: false, - }; + const promiseHooks = [ + [], // init + [], // before + [], // after + [], // resolve + ]; function setPromiseHooks(init, before, after, resolve) { - if (init) ArrayPrototypePush(promiseHooks.init, init); - if (before) ArrayPrototypePush(promiseHooks.before, before); - if (after) ArrayPrototypePush(promiseHooks.after, after); - if (resolve) ArrayPrototypePush(promiseHooks.resolve, resolve); + const hooks = [init, before, after, resolve]; + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i]; + // Skip if no callback was provided for this hook type. + if (hook == null) { + continue; + } + // Verify that the type of `hook` is a function. + if (typeof hook !== "function") { + throw new TypeError(`Expected function at position ${i}`); + } + // Crudely filter out no-op functions. + if ( + RegExpPrototypeTest(/\)(?:\s*|=>){}$/, FunctionPrototypeToString(hook)) + ) { + continue; + } + // Add the material hook to the list. + ArrayPrototypePush(promiseHooks[i], hook); + } - if (!promiseHooks.hasBeenSet) { - promiseHooks.hasBeenSet = true; + const wrappedHooks = ArrayPrototypeMap(promiseHooks, (hooks) => { + switch (hooks.length) { + case 0: + return noop; + case 1: + return hooks[0]; + case 2: + return create2xHookWrapper(hooks[0], hooks[1]); + case 3: + return create3xHookWrapper(hooks[0], hooks[1], hooks[2]); + default: + return createHookListWrapper(hooks); + } - ops.op_set_promise_hooks((promise, parentPromise) => { - for (let i = 0; i < promiseHooks.init.length; ++i) { - promiseHooks.init[i](promise, parentPromise); - } - }, (promise) => { - for (let i = 0; i < promiseHooks.before.length; ++i) { - promiseHooks.before[i](promise); - } - }, (promise) => { - for (let i = 0; i < promiseHooks.after.length; ++i) { - promiseHooks.after[i](promise); - } - }, (promise) => { - for (let i = 0; i < promiseHooks.resolve.length; ++i) { - promiseHooks.resolve[i](promise); + // The following functions are used to create wrapper functions that call + // all the hooks in a list of a certain length. The reason to use a + // function that creates a wrapper is to minimize the number of objects + // captured in the closure. + function create2xHookWrapper(hook1, hook2) { + return (promise, parent) => { + hook1(promise, parent); + hook2(promise, parent); + }; + } + function create3xHookWrapper(hook1, hook2, hook3) { + return (promise, parent) => { + hook1(promise, parent); + hook2(promise, parent); + hook3(promise, parent); + }; + } + function createHookListWrapper(hooks) { + for (let i = 0; i < hooks.length; i++) { + const hook = hooks[i]; + hook(promise, parent); } - }); - } + } + }); + + ops.op_set_promise_hooks( + wrappedHooks[0], + wrappedHooks[1], + wrappedHooks[2], + wrappedHooks[3], + ); } + function noop() {} + // Extra Deno.core.* exports const core = ObjectAssign(globalThis.Deno.core, { opAsync, @@ -397,28 +440,19 @@ runMicrotasks: () => ops.op_run_microtasks(), hasTickScheduled: () => ops.op_has_tick_scheduled(), setHasTickScheduled: (bool) => ops.op_set_has_tick_scheduled(bool), - evalContext: ( - source, - specifier, - ) => ops.op_eval_context(source, specifier), + evalContext: (source, specifier) => ops.op_eval_context(source, specifier), createHostObject: () => ops.op_create_host_object(), encode: (text) => ops.op_encode(text), decode: (buffer) => ops.op_decode(buffer), - serialize: ( - value, - options, - errorCallback, - ) => ops.op_serialize(value, options, errorCallback), + serialize: (value, options, errorCallback) => + ops.op_serialize(value, options, errorCallback), deserialize: (buffer, options) => ops.op_deserialize(buffer, options), getPromiseDetails: (promise) => ops.op_get_promise_details(promise), getProxyDetails: (proxy) => ops.op_get_proxy_details(proxy), isProxy: (value) => ops.op_is_proxy(value), memoryUsage: () => ops.op_memory_usage(), setWasmStreamingCallback: (fn) => ops.op_set_wasm_streaming_callback(fn), - abortWasmStreaming: ( - rid, - error, - ) => ops.op_abort_wasm_streaming(rid, error), + abortWasmStreaming: (rid, error) => ops.op_abort_wasm_streaming(rid, error), destructureError: (error) => ops.op_destructure_error(error), opNames: () => ops.op_op_names(), eventLoopHasMoreWork: () => ops.op_event_loop_has_more_work(),