Skip to content
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

refactor(core): new optimized op-layer using serde_v8 #9843

Merged
merged 66 commits into from
Mar 31, 2021

Conversation

AaronO
Copy link
Contributor

@AaronO AaronO commented Mar 20, 2021

This is the first PR in a series refactoring the op-layer to be both simpler and faster, it implements ideas shared in #9540

Keep in mind that given the breadth of changes, the intermediate code does not reflect the final intended state.

The bulk of the changes in this PR are scoped to core/

Key ideas

  • Use serde_v8 for a maximally efficient bijection between rust <> js values
  • Simplify and unify op-layer
    • Remove concept of custom op-encodings (no longer needed)
    • Handle op-metadata (errors, promise IDs) explicitly in the op-layer vs per op-encoding (aka: out-of-payload)
    • Remove shared queue & custom "asyncHandlers", all async values are returned in batches via js_recv_cb
    • The op-layer should be thought of as simple function calls with little indirection or translation besides the conceptually straightforward serde_v8 bijections
  • Preserve concepts of json/bin/min as semantic groups of their inputs/outputs instead of their op-encoding strategy, preserving these groups will also facilitate partial transitions over to v8 Fast API for the "min" and "bin" groups

Changes

  • Unify json and bin ops
    • Switch both to serde_v8
    • Common error handling
    • Common async return mechanisms (everything "overflows"
  • Change OpFn sig to ~= (state, payload, optional_buffer) -> serializable
  • But maintain retro-compatibility with all functions declared using reg_json_sync or similar
  • Adapt metrics wrapper and other misc places (plugins, ...)
  • serde_v8 tweaks (mainly to relax type-checking and be more permissive)
    • Handle deserializing enums in serde_v8 (after which only bytes-support would be unimplemented, intentionally)
  • Mainly rollback core.js to a prior simpler version
  • Strip shared queue code
    • From rust code
    • From JS code
  • Fix Dangling promise in async json ops when arg parsing fails #9914 when rewriting core.js
  • Comment out or remove JsRuntime tests regarding shared queue or old op-logic
  • Integration tests
    • Adapt broken/changed ones (metrics, sendAsyncStackTrace, ...)
    • Remove shared queue or buffer-encoding op tests
    • resolve_dns, broken due to [email protected] not implementing deserialize_enum
  • Move promise_id out of OpFn sig and out of OpResult
  • Update core's TS types
    • Make zeroCopy an optional arg instead of variadic
    • Add declarations for binOpSync/binOpAsync ?
  • Adapt http_bench_json_ops to follow best practices
  • Other adjustments for all tests to pass (e.g: diffs in JSON comparisons in LSP tests)
    • Match serde_json's handling of map-pairs with undefined values in serde_v8
    • Comment out/disable plugin tests (to fix in follw up
  • Last nitpicks and reviews
    • Drop / unwrap OpBuf, RcOpState type aliases
  • Make serde_v8 fail when deserializing non-utf8 strings (TODO: add failing tests)

Follow-up PRs

  1. Change op-sig, removing variadic BufVec
  2. More op-layer optimizations
  3. Revisit plugin ops (related to feat(plugin_api): Align DispatchOpFn signature to that of OpFn and access to OpState #9850)
  4. Tweak all ops to follow best-practices (i.e: avoid using serde_json::Value and json!())
  5. Optimize url_parse
  6. Optimize metrics_op since it increases our baseline op-overhead (accounting for nearly 50% of the baseline iirc, after all the other optimizations have been made)
  7. Optimize op_now by switching it to a json-op or introducing min-ops (first candidates for v8 Fast API)
  8. Add op baseline-overhead benches in core/ (similar to https://gist.github.com/AaronO/41803d5fc131417221a78c911cd3c6b0) (✅ Done: perf(core): op_baseline bench #9924)
  9. Consider renaming bin-ops to io-ops (possibly a better fit for their semantic group)
  10. Update docstrings, docs & core/README to reflect new op-layer
  11. Follow up on promiseTable performance (obj vs map vs ?)

Notes

  • This temporarily breaks http_bench_bin_ops, it will fail due to the runtime assertion of bin-ops requiring a non-null buffer arg. In practice all of deno's bin-ops follow this and this assertion will be removed by the follow-up PR 1. removing the variadic BufVec
  • This does not contain all of the op-layer optimizations
  • It aims to get a first working version with minimal breaking changes elsewhere

@AaronO AaronO mentioned this pull request Mar 24, 2021
7 tasks
core/core.js Outdated Show resolved Hide resolved
Handle missing values (undefined/nullish) by implementing visit_unit for ParseBooleanOrStringVec
Since otherwise casting f64 to u64 will fail, which can happen when unpacking serde_json::Values like in op_close (i.e: Value::as_u64)
@AaronO AaronO reopened this Mar 28, 2021
@AaronO AaronO changed the title refactor(core): new optimized op-ABI using serde_v8 refactor(core): new optimized op-layer using serde_v8 Mar 28, 2021
@AaronO AaronO changed the base branch from main to v1.8 March 28, 2021 03:14
@AaronO AaronO changed the base branch from v1.8 to main March 28, 2021 03:15
core/runtime.rs Outdated Show resolved Hide resolved
core/runtime.rs Outdated Show resolved Hide resolved
@ry
Copy link
Member

ry commented Mar 28, 2021

I've given this a cursory review and the structure looks good to me.

core/bindings.rs Show resolved Hide resolved
core/bindings.rs Outdated Show resolved Hide resolved
core/core.js Outdated Show resolved Hide resolved
core/core.js Outdated Show resolved Hide resolved
core/core.js Show resolved Hide resolved
core/plugin_api.rs Show resolved Hide resolved
core/runtime.rs Outdated Show resolved Hide resolved
runtime/metrics.rs Show resolved Hide resolved
runtime/ops/worker_host.rs Show resolved Hide resolved
serde_v8/src/de.rs Outdated Show resolved Hide resolved
Copy link
Member

@bartlomieju bartlomieju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

Huge thanks @AaronO! This work will be ground-breaking for the overall performance of the runtime and I'm looking forward to removing even more code in the follow up PRs.

@@ -1,411 +1,124 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome to see this file being 120 lines

@@ -56,12 +55,17 @@ pub use crate::modules::RecursiveModuleLoad;
pub use crate::normalize_path::normalize_path;
pub use crate::ops::op_close;
pub use crate::ops::op_resources;
pub use crate::ops::serialize_op_result;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this function should be public, what's the use case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make that private, I had left it public since it wasn't 100% clear how we'll approach plugin ops (though it seems like they won't need this and should be able to align plugins with ops without it)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it non-public for now

}

pub fn deserialize<T: DeserializeOwned>(self) -> Result<T, AnyError> {
serde_v8::from_v8(self.scope.unwrap(), self.value.unwrap())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that this function panics for OpPayload::empty()? A bit surpising

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In production these should never be None, we only use OpPayload::empty() for the op-table test, so we don't have to spin up a v8 isolate just to test that

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe add #[test] attribute to OpPayload::empty

Comment on lines +1392 to 1396
let async_responses_size = async_responses.len();
if async_responses_size == 0 {
return Ok(());
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: this conditional could potentially be removed after this PR - please see FIXME below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that FIXME seemed odd to me, since we call JsRuntime.core_js_init() in JsRuntime::new(), so I don't think this can ever happen unless you poll the runtime's event-loop while snapshotting ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there was a specific condition to trigger the problem and it was because of shared queue, but since shared queue is gone it shouldn't happen anymore.

core/runtime.rs Outdated Show resolved Hide resolved
@bartlomieju
Copy link
Member

@AaronO can you please draft a commit message for this PR

@ry ry merged commit fec1b2a into denoland:main Mar 31, 2021
@AaronO AaronO deleted the ops/a-new-hope branch March 31, 2021 14:57
AaronO added a commit to AaronO/deno that referenced this pull request Jul 23, 2021
ry pushed a commit that referenced this pull request Jul 23, 2021
hardfist pushed a commit to hardfist/deno that referenced this pull request Aug 7, 2024
- Improves op performance.
- Handle op-metadata (errors, promise IDs) explicitly in the op-layer vs
  per op-encoding (aka: out-of-payload).
- Remove shared queue & custom "asyncHandlers", all async values are
  returned in batches via js_recv_cb.
- The op-layer should be thought of as simple function calls with little
  indirection or translation besides the conceptually straightforward
  serde_v8 bijections.
- Preserve concepts of json/bin/min as semantic groups of their
  inputs/outputs instead of their op-encoding strategy, preserving these
  groups will also facilitate partial transitions over to v8 Fast API for the
  "min" and "bin" groups
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dangling promise in async json ops when arg parsing fails
4 participants