summaryrefslogtreecommitdiff
path: root/cli/cache
diff options
context:
space:
mode:
authorIgor Zinkovsky <igor@deno.com>2024-04-17 07:19:55 -0700
committerGitHub <noreply@github.com>2024-04-17 07:19:55 -0700
commitb3d7df55357ea6fc6f5141b64a9638ddb39b0f63 (patch)
tree0ca14140c7e080ed3367a7352bbaf3ed5f7a0f48 /cli/cache
parent9acbf90b06bf79dd6e4cf2428b3566da009bed65 (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.rs15
-rw-r--r--cli/cache/code_cache.rs231
-rw-r--r--cli/cache/deno_dir.rs6
-rw-r--r--cli/cache/mod.rs2
-rw-r--r--cli/cache/module_info.rs24
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,