diff options
author | Igor Zinkovsky <igor@deno.com> | 2024-04-17 07:19:55 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-17 07:19:55 -0700 |
commit | b3d7df55357ea6fc6f5141b64a9638ddb39b0f63 (patch) | |
tree | 0ca14140c7e080ed3367a7352bbaf3ed5f7a0f48 /cli/cache | |
parent | 9acbf90b06bf79dd6e4cf2428b3566da009bed65 (diff) |
perf: v8 code cache (#23081)
This PR enables V8 code cache for ES modules and for `require` scripts
through `op_eval_context`. Code cache artifacts are transparently stored
and fetched using sqlite db and are passed to V8. `--no-code-cache` can
be used to disable.
---------
Co-authored-by: Bartek IwaĆczuk <biwanczuk@gmail.com>
Diffstat (limited to 'cli/cache')
-rw-r--r-- | cli/cache/caches.rs | 15 | ||||
-rw-r--r-- | cli/cache/code_cache.rs | 231 | ||||
-rw-r--r-- | cli/cache/deno_dir.rs | 6 | ||||
-rw-r--r-- | cli/cache/mod.rs | 2 | ||||
-rw-r--r-- | cli/cache/module_info.rs | 24 |
5 files changed, 278 insertions, 0 deletions
diff --git a/cli/cache/caches.rs b/cli/cache/caches.rs index dc97f02d5..1be14b53b 100644 --- a/cli/cache/caches.rs +++ b/cli/cache/caches.rs @@ -8,6 +8,7 @@ use once_cell::sync::OnceCell; use super::cache_db::CacheDB; use super::cache_db::CacheDBConfiguration; use super::check::TYPE_CHECK_CACHE_DB; +use super::code_cache::CODE_CACHE_DB; use super::deno_dir::DenoDirProvider; use super::fast_check::FAST_CHECK_CACHE_DB; use super::incremental::INCREMENTAL_CACHE_DB; @@ -22,6 +23,7 @@ pub struct Caches { fast_check_db: OnceCell<CacheDB>, node_analysis_db: OnceCell<CacheDB>, type_checking_cache_db: OnceCell<CacheDB>, + code_cache_db: OnceCell<CacheDB>, } impl Caches { @@ -34,6 +36,7 @@ impl Caches { fast_check_db: Default::default(), node_analysis_db: Default::default(), type_checking_cache_db: Default::default(), + code_cache_db: Default::default(), } } @@ -124,4 +127,16 @@ impl Caches { .map(|dir| dir.type_checking_cache_db_file_path()), ) } + + pub fn code_cache_db(&self) -> CacheDB { + Self::make_db( + &self.code_cache_db, + &CODE_CACHE_DB, + self + .dir_provider + .get_or_create() + .ok() + .map(|dir| dir.code_cache_db_file_path()), + ) + } } diff --git a/cli/cache/code_cache.rs b/cli/cache/code_cache.rs new file mode 100644 index 000000000..5e44c366e --- /dev/null +++ b/cli/cache/code_cache.rs @@ -0,0 +1,231 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::AnyError; +use deno_runtime::code_cache; +use deno_runtime::deno_webstorage::rusqlite::params; + +use super::cache_db::CacheDB; +use super::cache_db::CacheDBConfiguration; +use super::cache_db::CacheFailure; + +pub static CODE_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { + table_initializer: "CREATE TABLE IF NOT EXISTS codecache ( + specifier TEXT NOT NULL, + type TEXT NOT NULL, + source_hash TEXT NOT NULL, + data BLOB NOT NULL, + PRIMARY KEY (specifier, type) + );", + on_version_change: "DELETE FROM codecache;", + preheat_queries: &[], + on_failure: CacheFailure::Blackhole, +}; + +#[derive(Clone)] +pub struct CodeCache { + inner: CodeCacheInner, +} + +impl CodeCache { + pub fn new(db: CacheDB) -> Self { + Self { + inner: CodeCacheInner::new(db), + } + } + + fn ensure_ok<T: Default>(res: Result<T, AnyError>) -> T { + match res { + Ok(x) => x, + Err(err) => { + // TODO(mmastrac): This behavior was inherited from before the refactoring but it probably makes sense to move it into the cache + // at some point. + // should never error here, but if it ever does don't fail + if cfg!(debug_assertions) { + panic!("Error using code cache: {err:#}"); + } else { + log::debug!("Error using code cache: {:#}", err); + } + T::default() + } + } + } + + pub fn get_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + source_hash: &str, + ) -> Option<Vec<u8>> { + Self::ensure_ok(self.inner.get_sync( + specifier, + code_cache_type, + source_hash, + )) + } + + pub fn set_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + source_hash: &str, + data: &[u8], + ) { + Self::ensure_ok(self.inner.set_sync( + specifier, + code_cache_type, + source_hash, + data, + )); + } +} + +impl code_cache::CodeCache for CodeCache { + fn get_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + source_hash: &str, + ) -> Option<Vec<u8>> { + self.get_sync(specifier, code_cache_type, source_hash) + } + + fn set_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + source_hash: &str, + data: &[u8], + ) { + self.set_sync(specifier, code_cache_type, source_hash, data); + } +} + +#[derive(Clone)] +struct CodeCacheInner { + conn: CacheDB, +} + +impl CodeCacheInner { + pub fn new(conn: CacheDB) -> Self { + Self { conn } + } + + pub fn get_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + source_hash: &str, + ) -> Result<Option<Vec<u8>>, AnyError> { + let query = " + SELECT + data + FROM + codecache + WHERE + specifier=?1 AND type=?2 AND source_hash=?3 + LIMIT 1"; + let params = params![specifier, code_cache_type.as_str(), source_hash,]; + self.conn.query_row(query, params, |row| { + let value: Vec<u8> = row.get(0)?; + Ok(value) + }) + } + + pub fn set_sync( + &self, + specifier: &str, + code_cache_type: code_cache::CodeCacheType, + source_hash: &str, + data: &[u8], + ) -> Result<(), AnyError> { + let sql = " + INSERT OR REPLACE INTO + codecache (specifier, type, source_hash, data) + VALUES + (?1, ?2, ?3, ?4)"; + let params = + params![specifier, code_cache_type.as_str(), source_hash, data]; + self.conn.execute(sql, params)?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + pub fn end_to_end() { + let conn = CacheDB::in_memory(&CODE_CACHE_DB, "1.0.0"); + let cache = CodeCacheInner::new(conn); + + assert!(cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + "hash", + ) + .unwrap() + .is_none()); + let data_esm = vec![1, 2, 3]; + cache + .set_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + "hash", + &data_esm, + ) + .unwrap(); + assert_eq!( + cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + "hash", + ) + .unwrap() + .unwrap(), + data_esm + ); + + assert!(cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + "hash", + ) + .unwrap() + .is_none()); + let data_script = vec![4, 5, 6]; + cache + .set_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + "hash", + &data_script, + ) + .unwrap(); + assert_eq!( + cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::Script, + "hash", + ) + .unwrap() + .unwrap(), + data_script + ); + assert_eq!( + cache + .get_sync( + "file:///foo/bar.js", + code_cache::CodeCacheType::EsModule, + "hash", + ) + .unwrap() + .unwrap(), + data_esm + ); + } +} diff --git a/cli/cache/deno_dir.rs b/cli/cache/deno_dir.rs index ee8c35684..b56dfbc89 100644 --- a/cli/cache/deno_dir.rs +++ b/cli/cache/deno_dir.rs @@ -142,6 +142,12 @@ impl DenoDir { self.root.join("npm") } + /// Path for the V8 code cache. + pub fn code_cache_db_file_path(&self) -> PathBuf { + // bump this version name to invalidate the entire cache + self.root.join("v8_code_cache_v1") + } + /// Path used for the REPL history file. /// Can be overridden or disabled by setting `DENO_REPL_HISTORY` environment variable. pub fn repl_history_file_path(&self) -> Option<PathBuf> { diff --git a/cli/cache/mod.rs b/cli/cache/mod.rs index 229a9cb54..a51179213 100644 --- a/cli/cache/mod.rs +++ b/cli/cache/mod.rs @@ -25,6 +25,7 @@ use std::time::SystemTime; mod cache_db; mod caches; mod check; +mod code_cache; mod common; mod deno_dir; mod disk_cache; @@ -37,6 +38,7 @@ mod parsed_source; pub use caches::Caches; pub use check::TypeCheckCache; +pub use code_cache::CodeCache; pub use common::FastInsecureHasher; pub use deno_dir::DenoDir; pub use deno_dir::DenoDirProvider; diff --git a/cli/cache/module_info.rs b/cli/cache/module_info.rs index 6d317b216..2e9274160 100644 --- a/cli/cache/module_info.rs +++ b/cli/cache/module_info.rs @@ -39,6 +39,7 @@ pub static MODULE_INFO_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { on_failure: CacheFailure::InMemory, }; +#[derive(Debug)] pub struct ModuleInfoCacheSourceHash(String); impl ModuleInfoCacheSourceHash { @@ -55,6 +56,12 @@ impl ModuleInfoCacheSourceHash { } } +impl From<ModuleInfoCacheSourceHash> for String { + fn from(source_hash: ModuleInfoCacheSourceHash) -> String { + source_hash.0 + } +} + /// A cache of `deno_graph::ModuleInfo` objects. Using this leads to a considerable /// performance improvement because when it exists we can skip parsing a module for /// deno_graph. @@ -80,6 +87,23 @@ impl ModuleInfoCache { } } + pub fn get_module_source_hash( + &self, + specifier: &ModuleSpecifier, + media_type: MediaType, + ) -> Result<Option<ModuleInfoCacheSourceHash>, AnyError> { + let query = "SELECT source_hash FROM moduleinfocache WHERE specifier=?1 AND media_type=?2"; + let res = self.conn.query_row( + query, + params![specifier.as_str(), serialize_media_type(media_type)], + |row| { + let source_hash: String = row.get(0)?; + Ok(ModuleInfoCacheSourceHash(source_hash)) + }, + )?; + Ok(res) + } + pub fn get_module_info( &self, specifier: &ModuleSpecifier, |