diff --git a/Cargo.lock b/Cargo.lock index 0a3397097b..ddeebf40f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,6 +309,7 @@ version = "0.16.0" dependencies = [ "measureme", "once_cell", + "rustc-hash", ] [[package]] diff --git a/boa_profiler/Cargo.toml b/boa_profiler/Cargo.toml index 59a5526116..f74c067e5d 100644 --- a/boa_profiler/Cargo.toml +++ b/boa_profiler/Cargo.toml @@ -11,8 +11,9 @@ repository.workspace = true rust-version.workspace = true [features] -profiler = ["measureme", "once_cell"] +profiler = ["measureme", "once_cell", "rustc-hash"] [dependencies] measureme = { version = "10.1.0", optional = true } once_cell = { version = "1.16.0", optional = true } +rustc-hash = { version = "1.1.0", optional = true } diff --git a/boa_profiler/src/lib.rs b/boa_profiler/src/lib.rs index 1eecc051ba..de9d550faf 100644 --- a/boa_profiler/src/lib.rs +++ b/boa_profiler/src/lib.rs @@ -81,10 +81,16 @@ use std::fmt::{self, Debug}; #[cfg(feature = "profiler")] -use measureme::{EventId, Profiler as MeasuremeProfiler, TimingGuard}; +use measureme::{EventId, Profiler as MeasuremeProfiler, StringId, TimingGuard}; #[cfg(feature = "profiler")] use once_cell::sync::OnceCell; #[cfg(feature = "profiler")] +use rustc_hash::FxHashMap; +#[cfg(feature = "profiler")] +use std::collections::hash_map::Entry; +#[cfg(feature = "profiler")] +use std::sync::RwLock; +#[cfg(feature = "profiler")] use std::{ path::Path, thread::{current, ThreadId}, @@ -94,6 +100,7 @@ use std::{ #[cfg(feature = "profiler")] pub struct Profiler { profiler: MeasuremeProfiler, + string_cache: RwLock>, } /// This static instance must never be public, and its only access must be done through the @@ -106,17 +113,45 @@ static mut INSTANCE: OnceCell = OnceCell::new(); impl Profiler { /// Start a new profiled event. pub fn start_event(&self, label: &str, category: &str) -> TimingGuard<'_> { - let kind = self.profiler.alloc_string(category); - let id = EventId::from_label(self.profiler.alloc_string(label)); + let kind = self.get_or_alloc_string(category); + let id = EventId::from_label(self.get_or_alloc_string(label)); let thread_id = Self::thread_id_to_u32(current().id()); self.profiler .start_recording_interval_event(kind, id, thread_id) } + fn get_or_alloc_string(&self, s: &str) -> StringId { + { + // Check the cache only with the read lock first. + let cache = self + .string_cache + .read() + .expect("Some writer panicked while holding an exclusive lock."); + if let Some(id) = cache.get(s) { + return *id; + } + } + let mut cache = self + .string_cache + .write() + .expect("Some writer panicked while holding an exclusive lock."); + let entry = cache.entry(s.into()); + match entry { + Entry::Occupied(entry) => *entry.get(), + Entry::Vacant(entry) => { + let id = self.profiler.alloc_string(s); + *entry.insert(id) + } + } + } + fn default() -> Self { let profiler = MeasuremeProfiler::new(Path::new("./my_trace")).expect("must be able to create file"); - Self { profiler } + Self { + profiler, + string_cache: RwLock::new(FxHashMap::default()), + } } /// Return the global instance of the profiler.