mirror of
https://github.com/denoland/deno.git
synced 2025-08-03 18:38:33 +00:00
perf(check): use v8 code cache for extension sources in deno check
(#28089)
In particular this helps startup of the TSC isolate because `00_typescript.js` can use the code cache. Overall, this offsets a fair bit of the hit we took when we removed the TSC snapshot. ``` ❯ hyperfine --warmup 5 -p "rm -rf ~/Library/Caches/deno/check_cache_v2" "./deno-this-pr check main.ts" "./deno-no-snapshot check main.ts" "./deno-with-snapshot check main.ts" Benchmark 1: ../../deno/target/release-lite/deno check main.ts Time (mean ± σ): 145.7 ms ± 3.6 ms [User: 347.6 ms, System: 36.9 ms] Range (min … max): 142.2 ms … 155.9 ms 19 runs Benchmark 2: ./deno-no-snapshot check main.ts Time (mean ± σ): 195.4 ms ± 3.3 ms [User: 397.7 ms, System: 34.9 ms] Range (min … max): 192.1 ms … 206.0 ms 15 runs Benchmark 3: ./deno-with-snapshot check main.ts Time (mean ± σ): 109.0 ms ± 2.2 ms [User: 155.9 ms, System: 19.3 ms] Range (min … max): 106.5 ms … 118.0 ms 26 runs Summary ./deno-with-snapshot check main.ts ran 1.34 ± 0.04 times faster than ./deno-this-pr check main.ts 1.79 ± 0.05 times faster than ./deno-no-snapshot check main.ts ```
This commit is contained in:
parent
55c5b07535
commit
33bccf9090
4 changed files with 235 additions and 16 deletions
|
@ -1828,6 +1828,7 @@ Unless --reload is specified, this command will not re-download already cached d
|
|||
)
|
||||
.defer(|cmd| {
|
||||
compile_args_without_check_args(cmd)
|
||||
.arg(no_code_cache_arg())
|
||||
.arg(
|
||||
Arg::new("all")
|
||||
.long("all")
|
||||
|
@ -4572,6 +4573,7 @@ fn check_parse(
|
|||
doc: matches.get_flag("doc"),
|
||||
doc_only: matches.get_flag("doc-only"),
|
||||
});
|
||||
flags.code_cache_enabled = !matches.get_flag("no-code-cache");
|
||||
allow_import_parse(flags, matches);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -7412,6 +7414,7 @@ mod tests {
|
|||
doc_only: false,
|
||||
}),
|
||||
type_check_mode: TypeCheckMode::Local,
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -7426,6 +7429,7 @@ mod tests {
|
|||
doc_only: false,
|
||||
}),
|
||||
type_check_mode: TypeCheckMode::Local,
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -7440,6 +7444,7 @@ mod tests {
|
|||
doc_only: true,
|
||||
}),
|
||||
type_check_mode: TypeCheckMode::Local,
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
@ -7468,6 +7473,7 @@ mod tests {
|
|||
doc_only: false,
|
||||
}),
|
||||
type_check_mode: TypeCheckMode::All,
|
||||
code_cache_enabled: true,
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
|
|
@ -873,6 +873,11 @@ impl CliFactory {
|
|||
self.npm_resolver().await?.clone(),
|
||||
self.sys(),
|
||||
self.tsconfig_resolver()?.clone(),
|
||||
if cli_options.code_cache_enabled() {
|
||||
Some(self.code_cache()?.clone())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)))
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -148,6 +148,7 @@ pub struct TypeChecker {
|
|||
npm_resolver: CliNpmResolver,
|
||||
sys: CliSys,
|
||||
tsconfig_resolver: Arc<TsConfigResolver>,
|
||||
code_cache: Option<Arc<crate::cache::CodeCache>>,
|
||||
}
|
||||
|
||||
impl TypeChecker {
|
||||
|
@ -162,6 +163,7 @@ impl TypeChecker {
|
|||
npm_resolver: CliNpmResolver,
|
||||
sys: CliSys,
|
||||
tsconfig_resolver: Arc<TsConfigResolver>,
|
||||
code_cache: Option<Arc<crate::cache::CodeCache>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
caches,
|
||||
|
@ -173,6 +175,7 @@ impl TypeChecker {
|
|||
npm_resolver,
|
||||
sys,
|
||||
tsconfig_resolver,
|
||||
code_cache,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,6 +286,7 @@ impl TypeChecker {
|
|||
grouped_roots,
|
||||
options,
|
||||
seen_diagnotics: Default::default(),
|
||||
code_cache: self.code_cache.clone(),
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
@ -433,6 +437,7 @@ struct DiagnosticsByFolderRealIterator<'a> {
|
|||
npm_check_state_hash: Option<u64>,
|
||||
seen_diagnotics: HashSet<String>,
|
||||
options: CheckOptions,
|
||||
code_cache: Option<Arc<crate::cache::CodeCache>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DiagnosticsByFolderRealIterator<'a> {
|
||||
|
@ -550,20 +555,27 @@ impl<'a> DiagnosticsByFolderRealIterator<'a> {
|
|||
let tsconfig_hash_data = FastInsecureHasher::new_deno_versioned()
|
||||
.write_hashable(ts_config)
|
||||
.finish();
|
||||
let response = tsc::exec(tsc::Request {
|
||||
config: ts_config.clone(),
|
||||
debug: self.log_level == Some(log::Level::Debug),
|
||||
graph: self.graph.clone(),
|
||||
hash_data: tsconfig_hash_data,
|
||||
maybe_npm: Some(tsc::RequestNpmState {
|
||||
cjs_tracker: self.cjs_tracker.clone(),
|
||||
node_resolver: self.node_resolver.clone(),
|
||||
npm_resolver: self.npm_resolver.clone(),
|
||||
}),
|
||||
maybe_tsbuildinfo,
|
||||
root_names,
|
||||
check_mode: self.options.type_check_mode,
|
||||
})?;
|
||||
let code_cache = self.code_cache.as_ref().map(|c| {
|
||||
let c: Arc<dyn deno_runtime::code_cache::CodeCache> = c.clone();
|
||||
c
|
||||
});
|
||||
let response = tsc::exec(
|
||||
tsc::Request {
|
||||
config: ts_config.clone(),
|
||||
debug: self.log_level == Some(log::Level::Debug),
|
||||
graph: self.graph.clone(),
|
||||
hash_data: tsconfig_hash_data,
|
||||
maybe_npm: Some(tsc::RequestNpmState {
|
||||
cjs_tracker: self.cjs_tracker.clone(),
|
||||
node_resolver: self.node_resolver.clone(),
|
||||
npm_resolver: self.npm_resolver.clone(),
|
||||
}),
|
||||
maybe_tsbuildinfo,
|
||||
root_names,
|
||||
check_mode: self.options.type_check_mode,
|
||||
},
|
||||
code_cache,
|
||||
)?;
|
||||
|
||||
let mut response_diagnostics = response.diagnostics.filter(|d| {
|
||||
self.should_include_diagnostic(self.options.type_check_mode, d)
|
||||
|
|
200
cli/tsc/mod.rs
200
cli/tsc/mod.rs
|
@ -4,6 +4,7 @@ use std::borrow::Cow;
|
|||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
|
@ -1372,10 +1373,82 @@ deno_core::extension!(deno_cli_tsc,
|
|||
}
|
||||
);
|
||||
|
||||
pub struct TscExtCodeCache {
|
||||
cache: Arc<dyn deno_runtime::code_cache::CodeCache>,
|
||||
}
|
||||
|
||||
impl TscExtCodeCache {
|
||||
pub fn new(cache: Arc<dyn deno_runtime::code_cache::CodeCache>) -> Self {
|
||||
Self { cache }
|
||||
}
|
||||
}
|
||||
|
||||
impl deno_core::ExtCodeCache for TscExtCodeCache {
|
||||
fn get_code_cache_info(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
code: &deno_core::ModuleSourceCode,
|
||||
esm: bool,
|
||||
) -> deno_core::SourceCodeCacheInfo {
|
||||
use deno_runtime::code_cache::CodeCacheType;
|
||||
let code_hash = FastInsecureHasher::new_deno_versioned()
|
||||
.write_hashable(code)
|
||||
.finish();
|
||||
let data = self
|
||||
.cache
|
||||
.get_sync(
|
||||
specifier,
|
||||
if esm {
|
||||
CodeCacheType::EsModule
|
||||
} else {
|
||||
CodeCacheType::Script
|
||||
},
|
||||
code_hash,
|
||||
)
|
||||
.map(Cow::from)
|
||||
.inspect(|_| {
|
||||
log::debug!(
|
||||
"V8 code cache hit for Extension module: {specifier}, [{code_hash:?}]"
|
||||
);
|
||||
});
|
||||
deno_core::SourceCodeCacheInfo {
|
||||
hash: code_hash,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
fn code_cache_ready(
|
||||
&self,
|
||||
specifier: ModuleSpecifier,
|
||||
source_hash: u64,
|
||||
code_cache: &[u8],
|
||||
esm: bool,
|
||||
) {
|
||||
use deno_runtime::code_cache::CodeCacheType;
|
||||
|
||||
log::debug!(
|
||||
"Updating V8 code cache for Extension module: {specifier}, [{source_hash:?}]"
|
||||
);
|
||||
self.cache.set_sync(
|
||||
specifier,
|
||||
if esm {
|
||||
CodeCacheType::EsModule
|
||||
} else {
|
||||
CodeCacheType::Script
|
||||
},
|
||||
source_hash,
|
||||
code_cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a request on the supplied snapshot, returning a response which
|
||||
/// contains information, like any emitted files, diagnostics, statistics and
|
||||
/// optionally an updated TypeScript build info.
|
||||
pub fn exec(request: Request) -> Result<Response, ExecError> {
|
||||
pub fn exec(
|
||||
request: Request,
|
||||
code_cache: Option<Arc<dyn deno_runtime::code_cache::CodeCache>>,
|
||||
) -> Result<Response, ExecError> {
|
||||
// tsc cannot handle root specifiers that don't have one of the "acceptable"
|
||||
// extensions. Therefore, we have to check the root modules against their
|
||||
// extensions and remap any that are unacceptable to tsc and add them to the
|
||||
|
@ -1417,10 +1490,14 @@ pub fn exec(request: Request) -> Result<Response, ExecError> {
|
|||
root_map,
|
||||
remapped_specifiers,
|
||||
));
|
||||
let extension_code_cache = code_cache.map(|cache| {
|
||||
Rc::new(TscExtCodeCache::new(cache)) as Rc<dyn deno_core::ExtCodeCache>
|
||||
});
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions,
|
||||
create_params: create_isolate_create_params(),
|
||||
startup_snapshot: deno_snapshots::CLI_SNAPSHOT,
|
||||
extension_code_cache,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
|
@ -1450,11 +1527,13 @@ pub fn exec(request: Request) -> Result<Response, ExecError> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deno_core::futures::future;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::OpState;
|
||||
use deno_error::JsErrorBox;
|
||||
use deno_graph::GraphKind;
|
||||
use deno_graph::ModuleGraph;
|
||||
use deno_runtime::code_cache::CodeCacheType;
|
||||
use test_util::PathRef;
|
||||
|
||||
use super::Diagnostic;
|
||||
|
@ -1529,6 +1608,12 @@ mod tests {
|
|||
|
||||
async fn test_exec(
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Result<Response, ExecError> {
|
||||
test_exec_with_cache(specifier, None).await
|
||||
}
|
||||
async fn test_exec_with_cache(
|
||||
specifier: &ModuleSpecifier,
|
||||
code_cache: Option<Arc<dyn deno_runtime::code_cache::CodeCache>>,
|
||||
) -> Result<Response, ExecError> {
|
||||
let hash_data = 123; // something random
|
||||
let fixtures = test_util::testdata_path().join("tsc2");
|
||||
|
@ -1563,7 +1648,7 @@ mod tests {
|
|||
root_names: vec![(specifier.clone(), MediaType::TypeScript)],
|
||||
check_mode: TypeCheckMode::All,
|
||||
};
|
||||
exec(request)
|
||||
exec(request, code_cache)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -1784,4 +1869,115 @@ mod tests {
|
|||
.expect("exec should not have errored");
|
||||
assert!(!actual.diagnostics.has_diagnostic());
|
||||
}
|
||||
|
||||
pub type SpecifierWithType = (ModuleSpecifier, CodeCacheType);
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestExtCodeCache {
|
||||
cache: Mutex<HashMap<(SpecifierWithType, u64), Vec<u8>>>,
|
||||
|
||||
hits: Mutex<HashMap<SpecifierWithType, usize>>,
|
||||
misses: Mutex<HashMap<SpecifierWithType, usize>>,
|
||||
}
|
||||
|
||||
impl deno_runtime::code_cache::CodeCache for TestExtCodeCache {
|
||||
fn get_sync(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
code_cache_type: CodeCacheType,
|
||||
source_hash: u64,
|
||||
) -> Option<Vec<u8>> {
|
||||
let result = self
|
||||
.cache
|
||||
.lock()
|
||||
.get(&((specifier.clone(), code_cache_type), source_hash))
|
||||
.cloned();
|
||||
if result.is_some() {
|
||||
*self
|
||||
.hits
|
||||
.lock()
|
||||
.entry((specifier.clone(), code_cache_type))
|
||||
.or_default() += 1;
|
||||
} else {
|
||||
*self
|
||||
.misses
|
||||
.lock()
|
||||
.entry((specifier.clone(), code_cache_type))
|
||||
.or_default() += 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn set_sync(
|
||||
&self,
|
||||
specifier: ModuleSpecifier,
|
||||
code_cache_type: CodeCacheType,
|
||||
source_hash: u64,
|
||||
data: &[u8],
|
||||
) {
|
||||
self
|
||||
.cache
|
||||
.lock()
|
||||
.insert(((specifier, code_cache_type), source_hash), data.to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_exec_code_cache() {
|
||||
let code_cache = Arc::new(TestExtCodeCache::default());
|
||||
let specifier = ModuleSpecifier::parse("https://deno.land/x/a.ts").unwrap();
|
||||
let actual = test_exec_with_cache(&specifier, Some(code_cache.clone()))
|
||||
.await
|
||||
.expect("exec should not have errored");
|
||||
assert!(!actual.diagnostics.has_diagnostic());
|
||||
|
||||
let expect = [
|
||||
(
|
||||
"ext:deno_cli_tsc/99_main_compiler.js",
|
||||
CodeCacheType::EsModule,
|
||||
),
|
||||
("ext:deno_cli_tsc/98_lsp.js", CodeCacheType::EsModule),
|
||||
("ext:deno_cli_tsc/97_ts_host.js", CodeCacheType::EsModule),
|
||||
("ext:deno_cli_tsc/00_typescript.js", CodeCacheType::Script),
|
||||
];
|
||||
|
||||
{
|
||||
let mut files = HashMap::new();
|
||||
|
||||
for (((specifier, ty), _), _) in code_cache.cache.lock().iter() {
|
||||
let specifier = specifier.to_string();
|
||||
if files.contains_key(&specifier) {
|
||||
panic!("should have only 1 entry per specifier");
|
||||
}
|
||||
files.insert(specifier, *ty);
|
||||
}
|
||||
|
||||
// 99_main_compiler, 98_lsp, 97_ts_host, 00_typescript
|
||||
assert_eq!(files.len(), 4);
|
||||
assert_eq!(code_cache.hits.lock().len(), 0);
|
||||
assert_eq!(code_cache.misses.lock().len(), 4);
|
||||
|
||||
for (specifier, ty) in &expect {
|
||||
assert_eq!(files.get(*specifier), Some(ty));
|
||||
}
|
||||
|
||||
code_cache.hits.lock().clear();
|
||||
code_cache.misses.lock().clear();
|
||||
}
|
||||
|
||||
{
|
||||
let _ = test_exec_with_cache(&specifier, Some(code_cache.clone()))
|
||||
.await
|
||||
.expect("exec should not have errored");
|
||||
|
||||
// 99_main_compiler, 98_lsp, 97_ts_host, 00_typescript
|
||||
assert_eq!(code_cache.hits.lock().len(), 4);
|
||||
assert_eq!(code_cache.misses.lock().len(), 0);
|
||||
|
||||
for (specifier, ty) in expect {
|
||||
let url = ModuleSpecifier::parse(specifier).unwrap();
|
||||
assert_eq!(code_cache.hits.lock().get(&(url, ty)), Some(&1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue