Skip to content

Commit

Permalink
perf: don't create duplicate function/object templates for ops (#1010)
Browse files Browse the repository at this point in the history
We were creating them twice - once when attaching them to
`Deno.core.ops`, then creating all new templates when setting up the
virtual ops module.

That meant that the snapshot included two copies of each template,
bloating the snapshot size and hurting snapshot deserialization time.

To fix this, just reuse the templates we've already attached to
`Deno.core.ops`.

This greatly improves the impact of object wrap (and ops in general) on
snapshot size and deserialization time.

statrtup perf:
with 1000 object wrap `DOMPoints` vs 1000 pure JS `DOMPoints`
(`dcore-objwrap-1000-opt` is this PR)
```
❯ hyperfine -w 500 -N "./dcore-js-1000 empty.js" "./dcore-objwrap-1000 empty.js" "./dcore-objwrap-1000-opt empty.js"

Benchmark 1: ./dcore-js-1000 empty.js
  Time (mean ± σ):      10.5 ms ±   0.5 ms    [User: 7.4 ms, System: 2.6 ms]
  Range (min … max):     9.5 ms …  11.7 ms    297 runs
 
Benchmark 2: ./dcore-objwrap-1000 empty.js
  Time (mean ± σ):      14.7 ms ±   0.7 ms    [User: 10.1 ms, System: 3.8 ms]
  Range (min … max):    13.4 ms …  17.5 ms    203 runs
 
Benchmark 3: ./dcore-objwrap-1000-opt empty.js
  Time (mean ± σ):      10.4 ms ±   0.6 ms    [User: 6.7 ms, System: 3.0 ms]
  Range (min … max):     9.3 ms …  13.7 ms    275 runs
 
Summary
  ./dcore-objwrap-1000-opt empty.js ran
    1.01 ± 0.08 times faster than ./dcore-js-1000 empty.js
    1.41 ± 0.11 times faster than ./dcore-objwrap-1000 empty.js
```

snapshot size (in particular the size of the isolate and context 1
snapshots):
```
❯ ./dcore-objwrap-1000 --v8-flags=--profile_deserialization empty.js
🛑 deno_core binary is meant for development and testing purposes.
Run empty.js
[Deserializing read-only space (420972 bytes) took 0.292 ms]
[Deserializing isolate (3510040 bytes) took 8.875 ms]
[Deserializing context #1 (1863760 bytes) took 5.333 ms]

❯ ./dcore-objwrap-1000-opt --v8-flags=--profile_deserialization empty.js
🛑 deno_core binary is meant for development and testing purposes.
Run empty.js
[Deserializing read-only space (420972 bytes) took 0.250 ms]
[Deserializing isolate (1865184 bytes) took 4.083 ms]
[Deserializing context #1 (1007968 bytes) took 2.666 ms]
```
  • Loading branch information
nathanwhit authored Dec 19, 2024
1 parent 4564ec3 commit b561653
Showing 1 changed file with 5 additions and 38 deletions.
43 changes: 5 additions & 38 deletions core/runtime/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -979,49 +979,16 @@ pub fn create_exports_for_ops_virtual_module<'s>(

let deno_obj = get(scope, global, DENO, "Deno");
let deno_core_obj = get(scope, deno_obj, CORE, "Deno.core");
let set_up_async_stub_fn: v8::Local<v8::Function> = get(
scope,
deno_core_obj,
SET_UP_ASYNC_STUB,
"Deno.core.setUpAsyncStub",
);

let undefined = v8::undefined(scope);
let ops_obj = get(scope, deno_core_obj, OPS, "Deno.core.ops");

for op_ctx in op_ctxs {
let name = op_ctx.decl.name_fast.v8_string(scope).unwrap();
let mut op_fn = op_ctx_function(scope, op_ctx);

// For async ops we need to set them up, by calling `Deno.core.setUpAsyncStub` -
// this call will generate an optimized function that binds to the provided
// op, while keeping track of promises and error remapping.
if op_ctx.decl.is_async {
let result = set_up_async_stub_fn
.call(scope, undefined.into(), &[name.into(), op_fn.into()])
.unwrap();
op_fn = result.try_into().unwrap()
}
exports.push((op_ctx.decl.name_fast, op_fn.into()));
let op_fn = get(scope, ops_obj, op_ctx.decl.name_fast, "op");
exports.push((op_ctx.decl.name_fast, op_fn));
}

for ctx in op_method_ctxs {
let tmpl = op_ctx_template(scope, &ctx.constructor);
let prototype = tmpl.prototype_template(scope);

let accessor_store = create_accessor_store(ctx);

for method in ctx.methods.iter() {
op_ctx_template_or_accessor(&accessor_store, scope, prototype, method);
}

for method in ctx.static_methods.iter() {
let op_fn = op_ctx_template(scope, method);
let method_key = method.decl.name_fast;
tmpl.set(method_key.v8_string(scope).unwrap().into(), op_fn.into());
}

let op_fn = tmpl.get_function(scope).unwrap();
exports.push((ctx.constructor.decl.name_fast, op_fn.into()));
let op_fn = get(scope, ops_obj, ctx.constructor.decl.name_fast, "op");
exports.push((ctx.constructor.decl.name_fast, op_fn));
}

exports
Expand Down

0 comments on commit b561653

Please sign in to comment.