refactor(core): More efficient serde for ES modules in snapshot (#17856)

Instead of relying on "serde_v8" which is very inefficient in
serializing enums, I'm hand rolling serde for "ModuleMap" data 
that is stored in the V8 snapshot to make ES modules 
snapshottable.

```
// this branch
Benchmark #2: ./target/release/deno run empty.js

  Time (mean ± σ):      21.4 ms ±   0.9 ms    [User: 15.6 ms, System: 6.4 ms]

  Range (min … max):    20.2 ms …  24.4 ms

// main branch
Benchmark #2: ./target/release/deno run empty.js

  Time (mean ± σ):      23.1 ms ±   1.2 ms    [User: 17.0 ms, System: 6.2 ms]

  Range (min … max):    21.0 ms …  26.0 ms

```
This commit is contained in:
Bartek Iwańczuk 2023-02-21 21:12:22 +01:00 committed by GitHub
parent 3479bc7661
commit 69c0b05f7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 229 additions and 71 deletions

View file

@ -161,6 +161,7 @@ fn json_module_evaluation_steps<'a>(
/// the module against an import assertion (if one is present
/// in the import statement).
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[repr(u32)]
pub enum ModuleType {
JavaScript,
Json,
@ -774,7 +775,7 @@ impl RecursiveModuleLoad {
self.visited.insert(module_request.clone());
while let Some((module_id, module_request)) = already_registered.pop_front()
{
let referrer = module_request.specifier.clone();
let referrer = ModuleSpecifier::parse(&module_request.specifier).unwrap();
let imports = self
.module_map_rc
.borrow()
@ -789,17 +790,15 @@ impl RecursiveModuleLoad {
) {
already_registered.push_back((module_id, module_request.clone()));
} else {
let referrer = referrer.clone();
let request = module_request.clone();
let specifier =
ModuleSpecifier::parse(&module_request.specifier).unwrap();
let referrer = referrer.clone();
let loader = self.loader.clone();
let is_dynamic_import = self.is_dynamic_import();
let fut = async move {
let load_result = loader
.load(
&request.specifier,
Some(referrer.clone()),
is_dynamic_import,
)
.load(&specifier, Some(referrer.clone()), is_dynamic_import)
.await;
load_result.map(|s| (request, s))
};
@ -851,7 +850,7 @@ impl Stream for RecursiveModuleLoad {
let asserted_module_type = inner.root_asserted_module_type.unwrap();
let module_type = inner.root_module_type.unwrap();
let module_request = ModuleRequest {
specifier: module_specifier.clone(),
specifier: module_specifier.to_string(),
asserted_module_type,
};
let module_source = ModuleSource {
@ -875,7 +874,7 @@ impl Stream for RecursiveModuleLoad {
_ => AssertedModuleType::JavaScriptOrWasm,
};
let module_request = ModuleRequest {
specifier: module_specifier.clone(),
specifier: module_specifier.to_string(),
asserted_module_type,
};
let loader = inner.loader.clone();
@ -905,6 +904,7 @@ impl Stream for RecursiveModuleLoad {
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[repr(u32)]
pub(crate) enum AssertedModuleType {
JavaScriptOrWasm,
Json,
@ -934,7 +934,7 @@ impl std::fmt::Display for AssertedModuleType {
/// which case this will have a `AssertedModuleType::Json`.
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub(crate) struct ModuleRequest {
pub specifier: ModuleSpecifier,
pub specifier: String,
pub asserted_module_type: AssertedModuleType,
}
@ -995,46 +995,94 @@ impl ModuleMap {
pub fn serialize_for_snapshotting(
&self,
scope: &mut v8::HandleScope,
) -> (v8::Global<v8::Object>, Vec<v8::Global<v8::Module>>) {
let obj = v8::Object::new(scope);
) -> (v8::Global<v8::Array>, Vec<v8::Global<v8::Module>>) {
let array = v8::Array::new(scope, 3);
let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap();
let next_load_id = v8::Integer::new(scope, self.next_load_id);
obj.set(scope, next_load_id_str.into(), next_load_id.into());
array.set_index(scope, 0, next_load_id.into());
let info_val = serde_v8::to_v8(scope, self.info.clone()).unwrap();
let info_str = v8::String::new(scope, "info").unwrap();
obj.set(scope, info_str.into(), info_val);
let info_arr = v8::Array::new(scope, self.info.len() as i32);
for (i, info) in self.info.iter().enumerate() {
let module_info_arr = v8::Array::new(scope, 5);
let by_name_triples: Vec<(String, AssertedModuleType, SymbolicModule)> =
self
.by_name
.clone()
.into_iter()
.map(|el| (el.0 .0, el.0 .1, el.1))
.collect();
let by_name_array = serde_v8::to_v8(scope, by_name_triples).unwrap();
let by_name_str = v8::String::new(scope, "by_name").unwrap();
obj.set(scope, by_name_str.into(), by_name_array);
let id = v8::Integer::new(scope, info.id as i32);
module_info_arr.set_index(scope, 0, id.into());
let obj_global = v8::Global::new(scope, obj);
let main = v8::Boolean::new(scope, info.main);
module_info_arr.set_index(scope, 1, main.into());
let name = v8::String::new(scope, &info.name).unwrap();
module_info_arr.set_index(scope, 2, name.into());
let array_len = 2 * info.requests.len() as i32;
let requests_arr = v8::Array::new(scope, array_len);
for (i, request) in info.requests.iter().enumerate() {
let specifier = v8::String::new(scope, &request.specifier).unwrap();
requests_arr.set_index(scope, 2 * i as u32, specifier.into());
let asserted_module_type =
v8::Integer::new(scope, request.asserted_module_type as i32);
requests_arr.set_index(
scope,
(2 * i) as u32 + 1,
asserted_module_type.into(),
);
}
module_info_arr.set_index(scope, 3, requests_arr.into());
let module_type = v8::Integer::new(scope, info.module_type as i32);
module_info_arr.set_index(scope, 4, module_type.into());
info_arr.set_index(scope, i as u32, module_info_arr.into());
}
array.set_index(scope, 1, info_arr.into());
let by_name_array = v8::Array::new(scope, self.by_name.len() as i32);
{
for (i, elem) in self.by_name.iter().enumerate() {
let arr = v8::Array::new(scope, 3);
let (specifier, asserted_module_type) = elem.0;
let specifier = v8::String::new(scope, specifier).unwrap();
arr.set_index(scope, 0, specifier.into());
let asserted_module_type =
v8::Integer::new(scope, *asserted_module_type as i32);
arr.set_index(scope, 1, asserted_module_type.into());
let symbolic_module: v8::Local<v8::Value> = match &elem.1 {
SymbolicModule::Alias(alias) => {
let alias = v8::String::new(scope, alias).unwrap();
alias.into()
}
SymbolicModule::Mod(id) => {
let id = v8::Integer::new(scope, *id as i32);
id.into()
}
};
arr.set_index(scope, 2, symbolic_module);
by_name_array.set_index(scope, i as u32, arr.into());
}
}
array.set_index(scope, 2, by_name_array.into());
let array_global = v8::Global::new(scope, array);
let handles = self.handles.clone();
(obj_global, handles)
(array_global, handles)
}
pub fn update_with_snapshot_data(
&mut self,
scope: &mut v8::HandleScope,
data: v8::Global<v8::Object>,
data: v8::Global<v8::Array>,
module_handles: Vec<v8::Global<v8::Module>>,
) {
let local_data: v8::Local<v8::Object> = v8::Local::new(scope, data);
let local_data: v8::Local<v8::Array> = v8::Local::new(scope, data);
{
let next_load_id_str = v8::String::new(scope, "next_load_id").unwrap();
let next_load_id =
local_data.get(scope, next_load_id_str.into()).unwrap();
let next_load_id = local_data.get_index(scope, 0).unwrap();
assert!(next_load_id.is_int32());
let integer = next_load_id.to_integer(scope).unwrap();
let val = integer.int32_value(scope).unwrap();
@ -1042,22 +1090,136 @@ impl ModuleMap {
}
{
let info_str = v8::String::new(scope, "info").unwrap();
let info_val = local_data.get(scope, info_str.into()).unwrap();
self.info = serde_v8::from_v8(scope, info_val).unwrap();
let info_val = local_data.get_index(scope, 1).unwrap();
let info_arr: v8::Local<v8::Array> = info_val.try_into().unwrap();
let len = info_arr.length() as usize;
let mut info = Vec::with_capacity(len);
for i in 0..len {
let module_info_arr: v8::Local<v8::Array> = info_arr
.get_index(scope, i as u32)
.unwrap()
.try_into()
.unwrap();
let id = module_info_arr
.get_index(scope, 0)
.unwrap()
.to_integer(scope)
.unwrap()
.value() as ModuleId;
let main = module_info_arr
.get_index(scope, 1)
.unwrap()
.to_boolean(scope)
.is_true();
let name = module_info_arr
.get_index(scope, 2)
.unwrap()
.to_rust_string_lossy(scope);
let requests_arr: v8::Local<v8::Array> = module_info_arr
.get_index(scope, 3)
.unwrap()
.try_into()
.unwrap();
let len = (requests_arr.length() as usize) / 2;
let mut requests = Vec::with_capacity(len);
for i in 0..len {
let specifier = requests_arr
.get_index(scope, (2 * i) as u32)
.unwrap()
.to_rust_string_lossy(scope);
let asserted_module_type_no = requests_arr
.get_index(scope, (2 * i + 1) as u32)
.unwrap()
.to_integer(scope)
.unwrap()
.value();
let asserted_module_type = match asserted_module_type_no {
0 => AssertedModuleType::JavaScriptOrWasm,
1 => AssertedModuleType::Json,
_ => unreachable!(),
};
requests.push(ModuleRequest {
specifier,
asserted_module_type,
});
}
let module_type_no = module_info_arr
.get_index(scope, 4)
.unwrap()
.to_integer(scope)
.unwrap()
.value();
let module_type = match module_type_no {
0 => ModuleType::JavaScript,
1 => ModuleType::Json,
_ => unreachable!(),
};
let module_info = ModuleInfo {
id,
main,
name,
requests,
module_type,
};
info.push(module_info);
}
self.info = info;
}
{
let by_name_str = v8::String::new(scope, "by_name").unwrap();
let by_name_data = local_data.get(scope, by_name_str.into()).unwrap();
let by_name_deser: Vec<(String, AssertedModuleType, SymbolicModule)> =
serde_v8::from_v8(scope, by_name_data).unwrap();
self.by_name = by_name_deser
.into_iter()
.map(|(name, module_type, symbolic_module)| {
((name, module_type), symbolic_module)
})
.collect();
let by_name_arr: v8::Local<v8::Array> =
local_data.get_index(scope, 2).unwrap().try_into().unwrap();
let len = by_name_arr.length() as usize;
let mut by_name = HashMap::with_capacity(len);
for i in 0..len {
let arr: v8::Local<v8::Array> = by_name_arr
.get_index(scope, i as u32)
.unwrap()
.try_into()
.unwrap();
let specifier =
arr.get_index(scope, 0).unwrap().to_rust_string_lossy(scope);
let asserted_module_type = match arr
.get_index(scope, 1)
.unwrap()
.to_integer(scope)
.unwrap()
.value()
{
0 => AssertedModuleType::JavaScriptOrWasm,
1 => AssertedModuleType::Json,
_ => unreachable!(),
};
let key = (specifier, asserted_module_type);
let symbolic_module_val = arr.get_index(scope, 2).unwrap();
let val = if symbolic_module_val.is_number() {
SymbolicModule::Mod(
symbolic_module_val
.to_integer(scope)
.unwrap()
.value()
.try_into()
.unwrap(),
)
} else {
SymbolicModule::Alias(symbolic_module_val.to_rust_string_lossy(scope))
};
by_name.insert(key, val);
}
self.by_name = by_name;
}
self.handles = module_handles;
@ -1223,7 +1385,7 @@ impl ModuleMap {
let asserted_module_type =
get_asserted_module_type_from_assertions(&assertions);
let request = ModuleRequest {
specifier: module_specifier,
specifier: module_specifier.to_string(),
asserted_module_type,
};
requests.push(request);
@ -1719,11 +1881,11 @@ import "/a.js";
modules.get_requested_modules(a_id),
Some(&vec![
ModuleRequest {
specifier: resolve_url("file:///b.js").unwrap(),
specifier: "file:///b.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},
ModuleRequest {
specifier: resolve_url("file:///c.js").unwrap(),
specifier: "file:///c.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},
])
@ -1731,14 +1893,14 @@ import "/a.js";
assert_eq!(
modules.get_requested_modules(b_id),
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///c.js").unwrap(),
specifier: "file:///c.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},])
);
assert_eq!(
modules.get_requested_modules(c_id),
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///d.js").unwrap(),
specifier: "file:///d.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},])
);
@ -1839,7 +2001,7 @@ import "/a.js";
assert_eq!(
imports,
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///b.js").unwrap(),
specifier: "file:///b.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},])
);
@ -1947,7 +2109,7 @@ import "/a.js";
assert_eq!(
imports,
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///b.json").unwrap(),
specifier: "file:///b.json".to_string(),
asserted_module_type: AssertedModuleType::Json,
},])
);
@ -2275,7 +2437,7 @@ import "/a.js";
assert_eq!(
modules.get_requested_modules(circular1_id),
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///circular2.js").unwrap(),
specifier: "file:///circular2.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}])
);
@ -2283,7 +2445,7 @@ import "/a.js";
assert_eq!(
modules.get_requested_modules(circular2_id),
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///circular3.js").unwrap(),
specifier: "file:///circular3.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}])
);
@ -2298,11 +2460,11 @@ import "/a.js";
modules.get_requested_modules(circular3_id),
Some(&vec![
ModuleRequest {
specifier: resolve_url("file:///circular1.js").unwrap(),
specifier: "file:///circular1.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},
ModuleRequest {
specifier: resolve_url("file:///circular2.js").unwrap(),
specifier: "file:///circular2.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}
])
@ -2522,11 +2684,11 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
modules.get_requested_modules(main_id),
Some(&vec![
ModuleRequest {
specifier: resolve_url("file:///b.js").unwrap(),
specifier: "file:///b.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
},
ModuleRequest {
specifier: resolve_url("file:///c.js").unwrap(),
specifier: "file:///c.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}
])
@ -2534,14 +2696,14 @@ if (import.meta.url != 'file:///main_with_code.js') throw Error();
assert_eq!(
modules.get_requested_modules(b_id),
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///c.js").unwrap(),
specifier: "file:///c.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}])
);
assert_eq!(
modules.get_requested_modules(c_id),
Some(&vec![ModuleRequest {
specifier: resolve_url("file:///d.js").unwrap(),
specifier: "file:///d.js".to_string(),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}])
);

View file

@ -438,7 +438,7 @@ impl JsRuntime {
fn get_context_data(
scope: &mut v8::HandleScope<()>,
context: v8::Local<v8::Context>,
) -> (Vec<v8::Global<v8::Module>>, v8::Global<v8::Object>) {
) -> (Vec<v8::Global<v8::Module>>, v8::Global<v8::Array>) {
fn data_error_to_panic(err: v8::DataError) -> ! {
match err {
v8::DataError::BadType { actual, expected } => {
@ -457,15 +457,11 @@ impl JsRuntime {
// The 0th element is the module map itself, followed by X number of module
// handles. We need to deserialize the "next_module_id" field from the
// map to see how many module handles we expect.
match scope.get_context_data_from_snapshot_once::<v8::Object>(0) {
match scope.get_context_data_from_snapshot_once::<v8::Array>(0) {
Ok(val) => {
let next_module_id = {
let info_str = v8::String::new(&mut scope, "info").unwrap();
let info_data: v8::Local<v8::Array> = val
.get(&mut scope, info_str.into())
.unwrap()
.try_into()
.unwrap();
let info_data: v8::Local<v8::Array> =
val.get_index(&mut scope, 1).unwrap().try_into().unwrap();
info_data.length()
};
@ -3648,7 +3644,7 @@ pub mod tests {
main,
name: specifier.to_string(),
requests: vec![crate::modules::ModuleRequest {
specifier: crate::resolve_url(&format!("file:///{prev}.js")).unwrap(),
specifier: format!("file:///{prev}.js"),
asserted_module_type: AssertedModuleType::JavaScriptOrWasm,
}],
module_type: ModuleType::JavaScript,