Skip to content

Commit

Permalink
cache common types for get-container
Browse files Browse the repository at this point in the history
  • Loading branch information
gwy15 committed Nov 27, 2022
1 parent 7797ad0 commit 7e2885f
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 1 deletion.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ quickcheck = "^0.9.2"
scaling = "0.1.3"
rand = "0.7.2"
serde_json = "1.0.87"
criterion = { version = "0.4", features = ["html_reports"] }

# [profile.release]
# debug = true
Expand All @@ -66,3 +67,8 @@ rustdoc-args = ["--cfg", "docsrs"]
name = "bench"
harness = false
required-features = ["bench"]

[[bench]]
name = "get_container"
harness = false
required-features = ["bench"]
84 changes: 84 additions & 0 deletions benches/get_container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! This is a benchmark to demonstrate that cached types (`String`, `str` as of now)
//! are faster than non-cached types because the lack of getting container from a dashmap.
//!
//! The results show a whopping 26% performance gain for short `ArcIntern<String>`.
use criterion::*;
use internment::ArcIntern;

const ITER: usize = 200_000;
const RANGE: usize = 20_000;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct NewType<T>(T);

fn bench_get_container(c: &mut Criterion) {
let mut group = c.benchmark_group("cached");
// time: [17.635 ms 17.707 ms 17.782 ms]
group.bench_function(BenchmarkId::new("String", "short"), |b| {
b.iter_batched(
|| {},
|_| {
let mut ans = Vec::with_capacity(RANGE);
for idx in 0..ITER {
let s = ArcIntern::<String>::new(format!("short-{}", idx % RANGE));
ans.push(s);
}
},
criterion::BatchSize::PerIteration,
);
});
group.finish();

let mut group = c.benchmark_group("uncached");
// time: [22.209 ms 22.294 ms 22.399 ms] => that's 26% faster!
group.bench_function(BenchmarkId::new("NewType<String>", "short"), |b| {
b.iter_batched(
|| {},
|_| {
let mut ans = Vec::with_capacity(RANGE);
for idx in 0..ITER {
let s = ArcIntern::<NewType<String>>::new(NewType(format!(
"short-{}",
idx % RANGE
)));
ans.push(s);
}
},
criterion::BatchSize::PerIteration,
);
});
// demonstrate that NewType does not affect performance
// time: [8.0247 ms 8.0419 ms 8.0607 ms]
group.bench_function(BenchmarkId::new("usize", "short"), |b| {
b.iter_batched(
|| {},
|_| {
let mut ans = Vec::with_capacity(RANGE);
for idx in 0..ITER {
let s = ArcIntern::<usize>::new(idx % RANGE);
ans.push(s);
}
},
criterion::BatchSize::PerIteration,
);
});
// time: [8.0210 ms 8.0341 ms 8.0485 ms] => no changes! NewType does not affect performance.
group.bench_function(BenchmarkId::new("NewType<usize>", "short"), |b| {
b.iter_batched(
|| {},
|_| {
let mut ans = Vec::with_capacity(RANGE);
for idx in 0..ITER {
let s = ArcIntern::<NewType<usize>>::new(NewType(idx % RANGE));
ans.push(s);
}
},
criterion::BatchSize::PerIteration,
);
});
group.finish();
}

criterion_group!(benches, bench_get_container);
criterion_main!(benches);
20 changes: 19 additions & 1 deletion src/arc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use ahash::RandomState;
use std::any::{Any, TypeId};
use std::fmt::{Debug, Display, Pointer};
use std::any::{Any, TypeId};
type Container<T> = DashMap<BoxRefCount<T>, (), RandomState>;
type Untyped = &'static (dyn Any + Send + Sync + 'static);
use std::borrow::Borrow;
Expand Down Expand Up @@ -107,6 +106,25 @@ impl<T: ?Sized + Eq + Hash + Send + Sync + 'static> ArcIntern<T> {
pub(crate) fn get_container() -> &'static Container<T> {
use once_cell::sync::OnceCell;
static ARC_CONTAINERS: OnceCell<DashMap<TypeId, Untyped, RandomState>> = OnceCell::new();

// make some shortcuts to speed up get_container.
macro_rules! common_containers {
($($t:ty),*) => {
$(
// hopefully this will be optimized away by compiler for types that are not matched.
// for matched types, this completely avoids the need to look up dashmap.
if TypeId::of::<T>() == TypeId::of::<$t>() {
static CONTAINER: OnceCell<Container<$t>> = OnceCell::new();
let c: &'static Container<$t> = CONTAINER.get_or_init(|| Container::with_hasher(RandomState::new()));
// SAFETY: we just compared to make sure `T` == `$t`.
// This converts Container<$t> to Container<T> to make the compiler happy.
return unsafe { &*((c as *const Container<$t>).cast::<Container<T>>()) };
}
)*
};
}
common_containers!(str, String);

let type_map = ARC_CONTAINERS.get_or_init(|| DashMap::with_hasher(RandomState::new()));
// Prefer taking the read lock to reduce contention, only use entry api if necessary.
let boxed = if let Some(boxed) = type_map.get(&TypeId::of::<T>()) {
Expand Down

0 comments on commit 7e2885f

Please sign in to comment.