-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discuss: Expose Structured Clone on v8
Module
#34355
Comments
v8
Modulev8
Module
Note that you can make the const { MessageChannel, receiveMessageOnPort } = require('worker_threads');
const { port1, port2 } = new MessageChannel();
function structuredClone (o) {
port1.postMessage(o);
return receiveMessageOnPort(port2).message;
}
const clone = structuredClone({ foo: 'bar' }); |
Found a similar discussion going on over in the whatwg repo: whatwg/html#793 There seems to be general consensus in the utility of a Great to know about |
If we can make it faster as a standalone function, that seems valuable. |
Here are flame charts for Interestingly – but probably as expected – they take approximately the same total time, but the synchronous version shuttles the copied object to Node.js while blocking the main thread where the async version does it in the background allowing Node to do other work. Async
SyncI was hoping that the synchronous call to @addaleax, since you implemented the original serialization bindings you may have some context on if there is some extra speed to be squeezed out of a standalone function – though to me this is looking more like just the cost of shuttling data around ¯\(ツ)/¯ If you have any top-of-mind ideas where a standalone function may improve perf I'm happy to brush off my C++ skills and take a stab at testing them out when I have time, otherwise I'm happy to keep this in user-land for now. |
@amiller-gh The serialization/deserialization steps create an intermediate Buffer where they need to write + read all strings and other data structures into. If all you want is structured clone, and you want it to be fast, then I think you can get a significant perf boost by implementing that in JS only – I think that’s basically what you’re looking for? |
I started with that approach, but this ended up being faster. I experimented using this function to clone objects: function deepClone<T>(o: T): T {
if (typeof o !== 'object') { return o; }
if (!o) { return o; }
// https://jsperf.com/deep-copy-vs-json-stringify-json-parse/25
if (Array.isArray(o)) {
const newO = [] as unknown as T;
for (let i = 0; i < o.length; i += 1) {
const val = (!o[i] || typeof o[i] !== 'object') ? o[i] : deepCloneSync(o[i]);
newO[i] = val === undefined ? null : val;
}
return newO;
}
const newO = {} as unknown as T;
for (const i of Object.keys(o)) {
const val = (!o[i] || typeof o[i] !== 'object') ? o[i] : deepCloneSync(o[i]);
if (val === undefined) { continue; }
newO[i] = val;
}
return newO;
} It's fast – certainly faster than This version of recursive deep clone is about as simple as you can make it – it doesn't handle complex objects, or cycles like structured clone does – so it was largely just a proof of concept as I was exploring options. Adding those extra features would only slow it down more. In addition, these objects I'm cloning eventually get sent over IPC anyway, so its convenient to use the same algorithm. Ideally I'd statically build cloning helper functions from my typescript types like I'm doing elsewhere, but unfortunately thats not possible here! |
Going to go ahead and self-close this one to help keep the backlog tidy :) I'm fairly convinced this is just the overhead cost of deep cloning, and the sync option outlined above means a standalone function is just syntactic sugar. If whatwg/html#793 progresses, Node.js may want to consider the extra Thank you both for the input! Hopefully this issue will help some intrepid developer Googling for Node.js deep clone optimization one day. |
Hi all! Relatively niche feature idea, but will likely be useful for a lot of lower level state management libraries. I'm a little out of my expertise with the C++ implementation here, so please excuse me if I'm missing something that is already possible using existing APIs.
Context
Deep cloning objects in javascript is notoriously hard – and even harder if you want to to be performant. Luckily we now have the Structured Clone Algorithm as a native option.
The native V8 implementation was exposed in the Node.js runtime in
nodejs@v8.0.0
as the Serialization API. Discussion happened in #6300Problem
Using the V8 serialization API, Node.js apps can run a structured clone like so:
However, for large objects this is actually still fairly slow! I assume this is because calling
v8.serialize/deserialize
shuttles data back and forth over the JS/C++ boundary twice. In my use case, I've found that a rather naive recursive clone function can actually out-perform it! Not ideal.Instead, I've discovered that taking the rather roundabout method of leveraging
MessageChannel
s can give me the native performance gains I'm expecting:MessageChannel
also uses the structured clone algorithm to pass data from one port to the next. However, it runs faster thanv8.serialize/deserialize
for this use case since it doesn't unnecessarily send data back and forth in order to clone the object.However, using
MessageChannel
is also not ideal:Proposed Solution
Relatively simply, we can choose to expose a sync and async API for V8's structured clone:
This should out-perform both
v8.serialize/deserialize
andMessageChannel
since it avoids the overhead of excess data shuttling andMessagePort
creation, while also enabling a fully synchronous API.Alternatives
MessageChannel
and publish as a user-land module, forgoing a synchronous, performant APIThe text was updated successfully, but these errors were encountered: