Skip to content

Commit

Permalink
perf: fast path for cached dyn imports (#906)
Browse files Browse the repository at this point in the history
This patch adds a fast path for already loaded and evaluated dynamic
imports by immediately resolving the promise and skipping most of the
event loop machinery.


![image](https://github.com/user-attachments/assets/6557277a-3c8a-4cee-b7b6-15b31fdbb7bd)


```js
// Warmup
await import('./other.mjs')
await import('./other.mjs')
await import('./other.mjs')
await import('./other.mjs')
await import('./other.mjs')

const start = Date.now()
for (let i = 0; i < 100000; i++) {
    await import('./other.mjs')
}
```
  • Loading branch information
littledivy authored Sep 17, 2024
1 parent 5530791 commit 3af141c
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 30 deletions.
50 changes: 34 additions & 16 deletions core/modules/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -941,16 +941,43 @@ impl ModuleMap {
// Initiate loading of a module graph imported using `import()`.
pub(crate) fn load_dynamic_import(
self: Rc<Self>,
scope: &mut v8::HandleScope,
specifier: &str,
referrer: &str,
requested_module_type: RequestedModuleType,
resolver_handle: v8::Global<v8::PromiseResolver>,
cped_handle: v8::Global<v8::Value>,
) {
) -> bool {
let resolve_result =
self.resolve(specifier, referrer, ResolutionKind::DynamicImport);

if let Ok(module_specifier) = &resolve_result {
if let Some(id) = self
.data
.borrow()
.get_id(module_specifier.as_str(), &requested_module_type)
{
let module = self
.data
.borrow()
.get_handle(id)
.map(|handle| v8::Local::new(scope, handle))
.expect("Dyn import module info not found");

if module.get_status() == v8::ModuleStatus::Evaluated {
let resolver = resolver_handle.open(scope);
let module_namespace = module.get_module_namespace();
resolver.resolve(scope, module_namespace).unwrap();

return false;
}
}
}

let load = RecursiveModuleLoad::dynamic_import(
specifier,
referrer,
requested_module_type.clone(),
requested_module_type,
self.clone(),
);

Expand All @@ -962,25 +989,16 @@ impl ModuleMap {
},
);

let resolve_result =
self.resolve(specifier, referrer, ResolutionKind::DynamicImport);
let fut = match resolve_result {
Ok(module_specifier) => {
if self
.data
.borrow()
.is_registered(module_specifier.as_str(), requested_module_type)
{
async move { (load.id, Ok(load)) }.boxed_local()
} else {
async move { (load.id, load.prepare().await.map(|()| load)) }
.boxed_local()
}
}
Ok(_) => async move { (load.id, load.prepare().await.map(|()| load)) }
.boxed_local(),
Err(error) => async move { (load.id, Err(error)) }.boxed_local(),
};

self.preparing_dynamic_imports.borrow_mut().push(fut);
self.preparing_dynamic_imports_pending.set(true);

true
}

pub(crate) fn has_pending_dynamic_imports(&self) -> bool {
Expand Down
10 changes: 0 additions & 10 deletions core/modules/module_map_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,6 @@ impl ModuleMapData {
}
}

pub fn is_registered(
&self,
specifier: &str,
requested_module_type: impl AsRef<RequestedModuleType>,
) -> bool {
self
.get_id(specifier, requested_module_type.as_ref())
.is_some()
}

pub(crate) fn alias(
&mut self,
name: FastString,
Expand Down
4 changes: 2 additions & 2 deletions core/modules/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,12 +1040,12 @@ async fn dyn_import_ok() {
runtime.poll_event_loop(cx, Default::default()),
Poll::Ready(Ok(_))
));
assert_eq!(loader.counts(), ModuleLoadEventCounts::new(7, 1, 1));
assert_eq!(loader.counts(), ModuleLoadEventCounts::new(5, 1, 1));
assert!(matches!(
runtime.poll_event_loop(cx, Default::default()),
Poll::Ready(Ok(_))
));
assert_eq!(loader.counts(), ModuleLoadEventCounts::new(7, 1, 1));
assert_eq!(loader.counts(), ModuleLoadEventCounts::new(5, 1, 1));
Poll::Ready(())
})
.await;
Expand Down
9 changes: 7 additions & 2 deletions core/runtime/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,14 +472,19 @@ pub fn host_import_module_dynamically_callback<'s>(
let state = JsRuntime::state_from(scope);
let module_map_rc = JsRealm::module_map_from(scope);

ModuleMap::load_dynamic_import(
if !ModuleMap::load_dynamic_import(
module_map_rc,
scope,
&specifier_str,
&referrer_name_str,
requested_module_type,
resolver_handle,
cped_handle,
);
) {
// Short-circuit if the module is already cached and we know it won't error.
return Some(promise);
}

state.notify_new_dynamic_import();
}
// Map errors from module resolution (not JS errors from module execution) to
Expand Down

0 comments on commit 3af141c

Please sign in to comment.