From ceecd8c495619284eee5763c1adb4afba345dceb Mon Sep 17 00:00:00 2001 From: Marcos Casagrande Date: Fri, 6 Oct 2023 23:21:48 +0200 Subject: [PATCH] perf(ext/web): optimize structuredClone without transferables (#20730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR optimizes `structuredClone` when it's called without transferables. ### Benchmarks **main** ``` cpu: 13th Gen Intel(R) Core(TM) i9-13900H runtime: deno 1.37.1 (x86_64-unknown-linux-gnu) benchmark time (avg) iter/s (min … max) p75 p99 p995 ----------------------------------------------------------------------------------- ----------------------------- structuredClone object 1.64 µs/iter 611,086.0 (1.58 µs … 1.84 µs) 1.66 µs 1.84 µs 1.84 µs structuredClone transferables 2.82 µs/iter 354,281.4 (2.78 µs … 2.92 µs) 2.84 µs 2.92 µs 2.92 µs ``` **this PR** ``` cpu: 13th Gen Intel(R) Core(TM) i9-13900H runtime: deno 1.37.1 (x86_64-unknown-linux-gnu) structuredClone object 1 µs/iter 998,383.5 (971.28 ns … 1.2 µs) 1 µs 1.2 µs 1.2 µs structuredClone transferables 2.82 µs/iter 355,087.5 (2.7 µs … 3.07 µs) 2.83 µs 3.07 µs 3.07 µs ``` ```js Deno.bench("structuredClone object", () => { structuredClone({ foo: "bar" }); }); Deno.bench("structuredClone transferables", () => { const buf = new Uint8Array([97]); structuredClone(buf, { transfer: [buf.buffer], }); }); ``` --- ext/web/13_message_port.js | 92 +++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/ext/web/13_message_port.js b/ext/web/13_message_port.js index 40145db784..ffbc48812a 100644 --- a/ext/web/13_message_port.js +++ b/ext/web/13_message_port.js @@ -205,34 +205,39 @@ function opCreateEntangledMessagePort() { function deserializeJsMessageData(messageData) { /** @type {object[]} */ const transferables = []; - const hostObjects = []; const arrayBufferIdsInTransferables = []; const transferredArrayBuffers = []; + let options; - for (let i = 0; i < messageData.transferables.length; ++i) { - const transferable = messageData.transferables[i]; - switch (transferable.kind) { - case "messagePort": { - const port = createMessagePort(transferable.data); - ArrayPrototypePush(transferables, port); - ArrayPrototypePush(hostObjects, port); - break; + if (messageData.transferables.length > 0) { + const hostObjects = []; + for (let i = 0; i < messageData.transferables.length; ++i) { + const transferable = messageData.transferables[i]; + switch (transferable.kind) { + case "messagePort": { + const port = createMessagePort(transferable.data); + ArrayPrototypePush(transferables, port); + ArrayPrototypePush(hostObjects, port); + break; + } + case "arrayBuffer": { + ArrayPrototypePush(transferredArrayBuffers, transferable.data); + const index = ArrayPrototypePush(transferables, null); + ArrayPrototypePush(arrayBufferIdsInTransferables, index); + break; + } + default: + throw new TypeError("Unreachable"); } - case "arrayBuffer": { - ArrayPrototypePush(transferredArrayBuffers, transferable.data); - const index = ArrayPrototypePush(transferables, null); - ArrayPrototypePush(arrayBufferIdsInTransferables, index); - break; - } - default: - throw new TypeError("Unreachable"); } + + options = { + hostObjects, + transferredArrayBuffers, + }; } - const data = core.deserialize(messageData.data, { - hostObjects, - transferredArrayBuffers, - }); + const data = core.deserialize(messageData.data, options); for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) { const id = arrayBufferIdsInTransferables[i]; @@ -248,31 +253,36 @@ function deserializeJsMessageData(messageData) { * @returns {messagePort.MessageData} */ function serializeJsMessageData(data, transferables) { + let options; const transferredArrayBuffers = []; - for (let i = 0, j = 0; i < transferables.length; i++) { - const ab = transferables[i]; - if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, ab)) { - if ( - ArrayBufferPrototypeGetByteLength(ab) === 0 && - ops.op_arraybuffer_was_detached(ab) - ) { - throw new DOMException( - `ArrayBuffer at index ${j} is already detached`, - "DataCloneError", - ); + if (transferables.length > 0) { + const hostObjects = []; + for (let i = 0, j = 0; i < transferables.length; i++) { + const t = transferables[i]; + if (ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, t)) { + if ( + ArrayBufferPrototypeGetByteLength(t) === 0 && + ops.op_arraybuffer_was_detached(t) + ) { + throw new DOMException( + `ArrayBuffer at index ${j} is already detached`, + "DataCloneError", + ); + } + j++; + ArrayPrototypePush(transferredArrayBuffers, t); + } else if (ObjectPrototypeIsPrototypeOf(MessagePortPrototype, t)) { + ArrayPrototypePush(hostObjects, t); } - j++; - ArrayPrototypePush(transferredArrayBuffers, ab); } + + options = { + hostObjects, + transferredArrayBuffers, + }; } - const serializedData = core.serialize(data, { - hostObjects: ArrayPrototypeFilter( - transferables, - (a) => ObjectPrototypeIsPrototypeOf(MessagePortPrototype, a), - ), - transferredArrayBuffers, - }, (err) => { + const serializedData = core.serialize(data, options, (err) => { throw new DOMException(err, "DataCloneError"); });