summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-02-22 01:55:31 +0100
committerGitHub <noreply@github.com>2023-02-22 06:25:31 +0530
commit4c6db7aa1493139f5a832c1e9ebfe44a1c80af80 (patch)
tree758a1e2b21fce29ac82bee66973853d75de8f950
parent5becfd6381889287ff16a064128021f87c8dfcb6 (diff)
perf(core, runtime): Further improve startup time (#17860)
This commit further improves startup time by: - no relying on "JsRuntime::execute_script" for runtime bootstrapping, this is instead done using V8 APIs directly - registering error classes during the snapshot time, instead of on startup Further improvements can be made, mainly around removing "core.initializeAsyncOps()" which takes around 2ms. This commit should result in ~1ms startup time improvement.
-rw-r--r--core/bindings.rs24
-rw-r--r--core/runtime.rs35
-rw-r--r--runtime/js/99_main.js129
-rw-r--r--runtime/web_worker.rs48
-rw-r--r--runtime/worker.rs35
-rw-r--r--runtime/worker_bootstrap.rs7
6 files changed, 184 insertions, 94 deletions
diff --git a/core/bindings.rs b/core/bindings.rs
index 50308b931..8ac308250 100644
--- a/core/bindings.rs
+++ b/core/bindings.rs
@@ -111,12 +111,29 @@ pub fn initialize_context<'s>(
let scope = &mut v8::ContextScope::new(scope, context);
+ let deno_str = v8::String::new(scope, "Deno").unwrap();
+ let core_str = v8::String::new(scope, "core").unwrap();
+ let ops_str = v8::String::new(scope, "ops").unwrap();
+
// Snapshot already registered `Deno.core.ops` but
// extensions may provide ops that aren't part of the snapshot.
if snapshot_options.loaded() {
// Grab the Deno.core.ops object & init it
- let ops_obj = JsRuntime::eval::<v8::Object>(scope, "Deno.core.ops")
- .expect("Deno.core.ops to exist");
+ let deno_obj: v8::Local<v8::Object> = global
+ .get(scope, deno_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let core_obj: v8::Local<v8::Object> = deno_obj
+ .get(scope, core_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let ops_obj: v8::Local<v8::Object> = core_obj
+ .get(scope, ops_str.into())
+ .expect("Deno.core.ops to exist")
+ .try_into()
+ .unwrap();
initialize_ops(scope, ops_obj, op_ctxs, snapshot_options);
if snapshot_options != SnapshotOptions::CreateFromExisting {
initialize_async_ops_info(scope, ops_obj, op_ctxs);
@@ -126,11 +143,9 @@ pub fn initialize_context<'s>(
// global.Deno = { core: { } };
let deno_obj = v8::Object::new(scope);
- let deno_str = v8::String::new(scope, "Deno").unwrap();
global.set(scope, deno_str.into(), deno_obj.into());
let core_obj = v8::Object::new(scope);
- let core_str = v8::String::new(scope, "core").unwrap();
deno_obj.set(scope, core_str.into(), core_obj.into());
// Bind functions to Deno.core.*
@@ -144,7 +159,6 @@ pub fn initialize_context<'s>(
// Bind functions to Deno.core.ops.*
let ops_obj = v8::Object::new(scope);
- let ops_str = v8::String::new(scope, "ops").unwrap();
core_obj.set(scope, ops_str.into(), ops_obj.into());
if !snapshot_options.will_snapshot() {
diff --git a/core/runtime.rs b/core/runtime.rs
index 9c6b7afea..c028d97c2 100644
--- a/core/runtime.rs
+++ b/core/runtime.rs
@@ -985,11 +985,36 @@ impl JsRuntime {
fn init_cbs(&mut self, realm: &JsRealm) {
let (recv_cb, build_custom_error_cb) = {
let scope = &mut realm.handle_scope(self.v8_isolate());
- let recv_cb =
- Self::eval::<v8::Function>(scope, "Deno.core.opresolve").unwrap();
- let build_custom_error_cb =
- Self::eval::<v8::Function>(scope, "Deno.core.buildCustomError")
- .expect("Deno.core.buildCustomError is undefined in the realm");
+ let context = realm.context();
+ let context_local = v8::Local::new(scope, context);
+ let global = context_local.global(scope);
+ let deno_str = v8::String::new(scope, "Deno").unwrap();
+ let core_str = v8::String::new(scope, "core").unwrap();
+ let opresolve_str = v8::String::new(scope, "opresolve").unwrap();
+ let build_custom_error_str =
+ v8::String::new(scope, "buildCustomError").unwrap();
+
+ let deno_obj: v8::Local<v8::Object> = global
+ .get(scope, deno_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let core_obj: v8::Local<v8::Object> = deno_obj
+ .get(scope, core_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+
+ let recv_cb: v8::Local<v8::Function> = core_obj
+ .get(scope, opresolve_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let build_custom_error_cb: v8::Local<v8::Function> = core_obj
+ .get(scope, build_custom_error_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
(
v8::Global::new(scope, recv_cb),
v8::Global::new(scope, build_custom_error_cb),
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index fa9b0a20d..da5b5f1b8 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -231,6 +231,69 @@ function formatException(error) {
}
}
+core.registerErrorClass("NotFound", errors.NotFound);
+core.registerErrorClass("PermissionDenied", errors.PermissionDenied);
+core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused);
+core.registerErrorClass("ConnectionReset", errors.ConnectionReset);
+core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted);
+core.registerErrorClass("NotConnected", errors.NotConnected);
+core.registerErrorClass("AddrInUse", errors.AddrInUse);
+core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable);
+core.registerErrorClass("BrokenPipe", errors.BrokenPipe);
+core.registerErrorClass("AlreadyExists", errors.AlreadyExists);
+core.registerErrorClass("InvalidData", errors.InvalidData);
+core.registerErrorClass("TimedOut", errors.TimedOut);
+core.registerErrorClass("Interrupted", errors.Interrupted);
+core.registerErrorClass("WouldBlock", errors.WouldBlock);
+core.registerErrorClass("WriteZero", errors.WriteZero);
+core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof);
+core.registerErrorClass("BadResource", errors.BadResource);
+core.registerErrorClass("Http", errors.Http);
+core.registerErrorClass("Busy", errors.Busy);
+core.registerErrorClass("NotSupported", errors.NotSupported);
+core.registerErrorBuilder(
+ "DOMExceptionOperationError",
+ function DOMExceptionOperationError(msg) {
+ return new DOMException(msg, "OperationError");
+ },
+);
+core.registerErrorBuilder(
+ "DOMExceptionQuotaExceededError",
+ function DOMExceptionQuotaExceededError(msg) {
+ return new DOMException(msg, "QuotaExceededError");
+ },
+);
+core.registerErrorBuilder(
+ "DOMExceptionNotSupportedError",
+ function DOMExceptionNotSupportedError(msg) {
+ return new DOMException(msg, "NotSupported");
+ },
+);
+core.registerErrorBuilder(
+ "DOMExceptionNetworkError",
+ function DOMExceptionNetworkError(msg) {
+ return new DOMException(msg, "NetworkError");
+ },
+);
+core.registerErrorBuilder(
+ "DOMExceptionAbortError",
+ function DOMExceptionAbortError(msg) {
+ return new DOMException(msg, "AbortError");
+ },
+);
+core.registerErrorBuilder(
+ "DOMExceptionInvalidCharacterError",
+ function DOMExceptionInvalidCharacterError(msg) {
+ return new DOMException(msg, "InvalidCharacterError");
+ },
+);
+core.registerErrorBuilder(
+ "DOMExceptionDataError",
+ function DOMExceptionDataError(msg) {
+ return new DOMException(msg, "DataError");
+ },
+);
+
function runtimeStart(runtimeOptions, source) {
core.setMacrotaskCallback(timers.handleTimerMacrotask);
core.setMacrotaskCallback(promiseRejectMacrotaskCallback);
@@ -247,72 +310,6 @@ function runtimeStart(runtimeOptions, source) {
colors.setNoColor(runtimeOptions.noColor || !runtimeOptions.isTty);
// deno-lint-ignore prefer-primordials
Error.prepareStackTrace = core.prepareStackTrace;
- registerErrors();
-}
-
-function registerErrors() {
- core.registerErrorClass("NotFound", errors.NotFound);
- core.registerErrorClass("PermissionDenied", errors.PermissionDenied);
- core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused);
- core.registerErrorClass("ConnectionReset", errors.ConnectionReset);
- core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted);
- core.registerErrorClass("NotConnected", errors.NotConnected);
- core.registerErrorClass("AddrInUse", errors.AddrInUse);
- core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable);
- core.registerErrorClass("BrokenPipe", errors.BrokenPipe);
- core.registerErrorClass("AlreadyExists", errors.AlreadyExists);
- core.registerErrorClass("InvalidData", errors.InvalidData);
- core.registerErrorClass("TimedOut", errors.TimedOut);
- core.registerErrorClass("Interrupted", errors.Interrupted);
- core.registerErrorClass("WouldBlock", errors.WouldBlock);
- core.registerErrorClass("WriteZero", errors.WriteZero);
- core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof);
- core.registerErrorClass("BadResource", errors.BadResource);
- core.registerErrorClass("Http", errors.Http);
- core.registerErrorClass("Busy", errors.Busy);
- core.registerErrorClass("NotSupported", errors.NotSupported);
- core.registerErrorBuilder(
- "DOMExceptionOperationError",
- function DOMExceptionOperationError(msg) {
- return new DOMException(msg, "OperationError");
- },
- );
- core.registerErrorBuilder(
- "DOMExceptionQuotaExceededError",
- function DOMExceptionQuotaExceededError(msg) {
- return new DOMException(msg, "QuotaExceededError");
- },
- );
- core.registerErrorBuilder(
- "DOMExceptionNotSupportedError",
- function DOMExceptionNotSupportedError(msg) {
- return new DOMException(msg, "NotSupported");
- },
- );
- core.registerErrorBuilder(
- "DOMExceptionNetworkError",
- function DOMExceptionNetworkError(msg) {
- return new DOMException(msg, "NetworkError");
- },
- );
- core.registerErrorBuilder(
- "DOMExceptionAbortError",
- function DOMExceptionAbortError(msg) {
- return new DOMException(msg, "AbortError");
- },
- );
- core.registerErrorBuilder(
- "DOMExceptionInvalidCharacterError",
- function DOMExceptionInvalidCharacterError(msg) {
- return new DOMException(msg, "InvalidCharacterError");
- },
- );
- core.registerErrorBuilder(
- "DOMExceptionDataError",
- function DOMExceptionDataError(msg) {
- return new DOMException(msg, "DataError");
- },
- );
}
const pendingRejections = [];
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index bcf999b3b..353c4a442 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -318,6 +318,7 @@ pub struct WebWorker {
pub worker_type: WebWorkerType,
pub main_module: ModuleSpecifier,
poll_for_messages_fn: Option<v8::Global<v8::Value>>,
+ bootstrap_fn_global: Option<v8::Global<v8::Function>>,
}
pub struct WebWorkerOptions {
@@ -496,6 +497,25 @@ impl WebWorker {
(internal_handle, external_handle)
};
+ let bootstrap_fn_global = {
+ let context = js_runtime.global_context();
+ let scope = &mut js_runtime.handle_scope();
+ let context_local = v8::Local::new(scope, context);
+ let global_obj = context_local.global(scope);
+ let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap();
+ let bootstrap_ns: v8::Local<v8::Object> = global_obj
+ .get(scope, bootstrap_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let main_runtime_str = v8::String::new(scope, "workerRuntime").unwrap();
+ let bootstrap_fn =
+ bootstrap_ns.get(scope, main_runtime_str.into()).unwrap();
+ let bootstrap_fn =
+ v8::Local::<v8::Function>::try_from(bootstrap_fn).unwrap();
+ v8::Global::new(scope, bootstrap_fn)
+ };
+
(
Self {
id: worker_id,
@@ -505,6 +525,7 @@ impl WebWorker {
worker_type: options.worker_type,
main_module,
poll_for_messages_fn: None,
+ bootstrap_fn_global: Some(bootstrap_fn_global),
},
external_handle,
)
@@ -513,15 +534,24 @@ impl WebWorker {
pub fn bootstrap(&mut self, options: &BootstrapOptions) {
// Instead of using name for log we use `worker-${id}` because
// WebWorkers can have empty string as name.
- let script = format!(
- "bootstrap.workerRuntime({}, \"{}\", \"{}\")",
- options.as_json(),
- self.name,
- self.id
- );
- self
- .execute_script(&located_script_name!(), &script)
- .expect("Failed to execute worker bootstrap script");
+ {
+ let scope = &mut self.js_runtime.handle_scope();
+ let options_v8 =
+ deno_core::serde_v8::to_v8(scope, options.as_json()).unwrap();
+ let bootstrap_fn = self.bootstrap_fn_global.take().unwrap();
+ let bootstrap_fn = v8::Local::new(scope, bootstrap_fn);
+ let undefined = v8::undefined(scope);
+ let name_str: v8::Local<v8::Value> =
+ v8::String::new(scope, &self.name).unwrap().into();
+ let id_str: v8::Local<v8::Value> =
+ v8::String::new(scope, &format!("{}", self.id))
+ .unwrap()
+ .into();
+ bootstrap_fn
+ .call(scope, undefined.into(), &[options_v8, name_str, id_str])
+ .unwrap();
+ }
+ // TODO(bartlomieju): this could be done using V8 API, without calling `execute_script`.
// Save a reference to function that will start polling for messages
// from a worker host; it will be called after the user code is loaded.
let script = r#"
diff --git a/runtime/worker.rs b/runtime/worker.rs
index d1998cd88..908f1a7ab 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -14,7 +14,6 @@ use deno_cache::SqliteBackedCache;
use deno_core::error::AnyError;
use deno_core::error::JsError;
use deno_core::futures::Future;
-use deno_core::located_script_name;
use deno_core::v8;
use deno_core::CompiledWasmModuleStore;
use deno_core::Extension;
@@ -66,6 +65,7 @@ pub struct MainWorker {
should_break_on_first_statement: bool,
should_wait_for_inspector_session: bool,
exit_code: ExitCode,
+ bootstrap_fn_global: Option<v8::Global<v8::Function>>,
}
pub struct WorkerOptions {
@@ -318,20 +318,45 @@ impl MainWorker {
op_state.borrow_mut().put(inspector);
}
+ let bootstrap_fn_global = {
+ let context = js_runtime.global_context();
+ let scope = &mut js_runtime.handle_scope();
+ let context_local = v8::Local::new(scope, context);
+ let global_obj = context_local.global(scope);
+ let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap();
+ let bootstrap_ns: v8::Local<v8::Object> = global_obj
+ .get(scope, bootstrap_str.into())
+ .unwrap()
+ .try_into()
+ .unwrap();
+ let main_runtime_str = v8::String::new(scope, "mainRuntime").unwrap();
+ let bootstrap_fn =
+ bootstrap_ns.get(scope, main_runtime_str.into()).unwrap();
+ let bootstrap_fn =
+ v8::Local::<v8::Function>::try_from(bootstrap_fn).unwrap();
+ v8::Global::new(scope, bootstrap_fn)
+ };
+
Self {
js_runtime,
should_break_on_first_statement: options.should_break_on_first_statement,
should_wait_for_inspector_session: options
.should_wait_for_inspector_session,
exit_code,
+ bootstrap_fn_global: Some(bootstrap_fn_global),
}
}
pub fn bootstrap(&mut self, options: &BootstrapOptions) {
- let script = format!("bootstrap.mainRuntime({})", options.as_json());
- self
- .execute_script(&located_script_name!(), &script)
- .expect("Failed to execute bootstrap script");
+ let scope = &mut self.js_runtime.handle_scope();
+ let options_v8 =
+ deno_core::serde_v8::to_v8(scope, options.as_json()).unwrap();
+ let bootstrap_fn = self.bootstrap_fn_global.take().unwrap();
+ let bootstrap_fn = v8::Local::new(scope, bootstrap_fn);
+ let undefined = v8::undefined(scope);
+ bootstrap_fn
+ .call(scope, undefined.into(), &[options_v8])
+ .unwrap();
}
/// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script)
diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs
index 5563b6ead..12abceca6 100644
--- a/runtime/worker_bootstrap.rs
+++ b/runtime/worker_bootstrap.rs
@@ -58,8 +58,8 @@ impl Default for BootstrapOptions {
}
impl BootstrapOptions {
- pub fn as_json(&self) -> String {
- let payload = json!({
+ pub fn as_json(&self) -> serde_json::Value {
+ json!({
// Shared bootstrap args
"args": self.args,
"cpuCount": self.cpu_count,
@@ -80,7 +80,6 @@ impl BootstrapOptions {
"v8Version": deno_core::v8_version(),
"userAgent": self.user_agent,
"inspectFlag": self.inspect,
- });
- serde_json::to_string_pretty(&payload).unwrap()
+ })
}
}