-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
core: introduce serde_v8 #9722
core: introduce serde_v8 #9722
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll need to look at serde_v8 in more detail but about this PR:
-
On the one hand, it removes quite a bit of code, which is definitely nice.
-
On the other hand, the serialization feels a little magical.
Things like tuple structs getting marshalled into JS arrays is kind of surprising to me but I also kind of see why it makes sense. Maybe I just need to get used to it.
For reference, serde_json has the same behavior. |
@bnoordhuis I completely understand the sentiment, however I don't think "feeling magical" is a negative here. I think serialization layers can be broadly scored on: expressiveness, consistency/reliability and efficiency.
Whilst applying it to the core functions wasn't the original intention, it's a natural fit anywhere we're mapping I personally found parts of This PR does make specific changes to Context on op-encoding perfs: The latest benches, show that using |
I think it could also be neat to have We could also add With Upside is that it would be "typed", you could grasp the structure at a glance and serde would handle camelCase-ification and other details. |
I left a comment on the relevant commit in serde_v8 but for posterity: the problem with interned strings is that they leak memory unless you know in advance that the set of strings is bounded. I don't know if that's true for deno, but even if it's true now, it might not stay true going forward. |
@bnoordhuis Thanks for the feedback! Ultimately the plan is to use our own I haven't implemented Maps in We could also use regular strings for this first release if you're concerned about the (potentially) unbounded mem-usage. They are naturally slower and will cause higher GC-pressure, but overall it's still faster than the current implementation Quick benchesInternalized Strings
Regular Strings
Bench conclusionsRegular v8 strings are ~20-40% slower on these benches (decoding struct keys), but it's still an order of magnitude faster than the current implementation. So I think we can ship regular strings in the first release and then dedupe with our own bounded KeyCache in the future. |
@AaronO what's is your idea for further integration? Ie. which parts of codebase could benefit the most from introducing I wonder if you could bring |
c74d7d0
to
f29b7f9
Compare
@bartlomieju
Beyond the op-ABI, it could further be used in |
Reduce encoding verbosity of some core functions
Mainly removed ABI stuff and benches (used nightly toolchain)
91360e6
to
057e915
Compare
To support serializing `json!()` and `serde_json::Value`
Fixes de_unit_undefined test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AaronO could you please add copyrights to the files?
Magic(MagicSerializer<'a, 'b>), | ||
Regular(ObjectSerializer<'a, 'b>), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between those two? Maybe a comment here could help
use rusty_v8 as v8; | ||
use std::sync::Once; | ||
|
||
pub fn js_exec<'s>( | ||
scope: &mut v8::HandleScope<'s>, | ||
src: &str, | ||
) -> v8::Local<'s, v8::Value> { | ||
let code = v8::String::new(scope, src).unwrap(); | ||
let script = v8::Script::compile(scope, code, None).unwrap(); | ||
script.run(scope).unwrap() | ||
} | ||
|
||
pub fn v8_init() { | ||
let platform = v8::new_default_platform().unwrap(); | ||
v8::V8::initialize_platform(platform); | ||
v8::V8::initialize(); | ||
} | ||
|
||
pub fn v8_shutdown() { | ||
unsafe { | ||
v8::V8::dispose(); | ||
} | ||
v8::V8::shutdown_platform(); | ||
} | ||
|
||
pub fn v8_do(f: impl FnOnce()) { | ||
static V8_INIT: Once = Once::new(); | ||
V8_INIT.call_once(|| { | ||
v8_init(); | ||
}); | ||
f(); | ||
// v8_shutdown(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move contents of this file to serde_v8/tests
macro_rules! not_reachable { | ||
($($name:ident($ty:ty, $lt:lifetime);)*) => { | ||
$(fn $name(self, _v: $ty) -> JsResult<$lt> { | ||
unreachable!(); | ||
})* | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this macro is superfluous, it would be more readable (although repetitive) to spell out those methods explicitly
use std::convert::TryFrom; | ||
|
||
#[derive(Deserialize)] | ||
struct MagicOp<'s> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does magic have some special meaning in this file?
type SerializeStructVariant = | ||
VariantSerializer<'a, 'b, StructSerializers<'a, 'b>>; | ||
|
||
forward_to! { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, could you please remove this macro in favor of repeating method defintions?
Passthrough to deserialize_string since we only return owned strings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Aaron, this is an amazing piece of code artistry. We've talked about this idea before, but never took it seriously. We thought it would be too hard - thought it wouldn't pay off. We were wrong on both accounts as you have demonstrated here.
I have benchmarked http_bench_json_ops on main and #9843 and see pretty massive improvements: https://gist.github.com/ry/c4008dc9d7a8a08bce1fdbad7d70e8e0
Once we replace JSON ops with serde_v8, this will be the single biggest performance improvement in Deno in two years. I suspect it will vastly simplify a lot of the juggling in deno_core.
It may not be very visible to users immediately, but this really changes the game.
} | ||
} | ||
|
||
// from_v8 deserializes a v8::Value into a Deserializable / rust struct |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use triple slash comments here and elsewhere.
This PR introduces
serde_v8
intocore
and uses it to reduce verbosity (making code arguably more readable and rusty).It mainly serves to showcase
serde_v8
as an early alpha and gather feedback before using it for a new op-ABI.Subtasks
serde_v8
serde_v8::Value
for passthrough (implementation is pretty "hacky", uses transmute)get_proxy_details
,get_promise_details
,eval_context
)serde_v8
into repo's tree (best mid-term solution IMO)json!()
compatserialize_map
anddeserialize_map
(mainly to supportserde_json::Value
, HashMaps, etc...)Follow-ups
In future PRs:
serde_v8
to reduce verbosity ofJsStackFrame
decodingbencher
orcriterion.rs
)serde_v8
to optimize op-ABI (refactor(core): new optimized op-layer using serde_v8 #9843)Related to #9540