Browse Source

[profiler] Cache StringId (#2495)

boa_profiler was allocating new strings every time even when they are the same string, which leads to a crash when `start_event` is called too much. This change updates the profiler to use a hash map cache and avoid duplicate string allocations.
pull/2510/head
Choongwoo Han 1 year ago
parent
commit
95a1d9e54f
  1. 1
      Cargo.lock
  2. 3
      boa_profiler/Cargo.toml
  3. 43
      boa_profiler/src/lib.rs

1
Cargo.lock generated

@ -309,6 +309,7 @@ version = "0.16.0"
dependencies = [
"measureme",
"once_cell",
"rustc-hash",
]
[[package]]

3
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 }

43
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<FxHashMap<String, StringId>>,
}
/// This static instance must never be public, and its only access must be done through the
@ -106,17 +113,45 @@ static mut INSTANCE: OnceCell<Profiler> = 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.

Loading…
Cancel
Save