diff --git a/benches/get_container.rs b/benches/get_container.rs index 36b5d41..9db3736 100644 --- a/benches/get_container.rs +++ b/benches/get_container.rs @@ -14,14 +14,45 @@ struct NewType(T); fn bench_get_container(c: &mut Criterion) { let mut group = c.benchmark_group("cached"); + let vals: Vec<_> = (0..RANGE).map(|idx| format!("short-{}", idx)).collect(); + // 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); + let mut ans = Vec::with_capacity(ITER); + for idx in 0..ITER { + let s = ArcIntern::::new(vals[idx % RANGE].clone()); + ans.push(s); + } + }, + criterion::BatchSize::PerIteration, + ); + }); + group.bench_function(BenchmarkId::new("String", "short-from_ref"), |b| { + b.iter_batched( + || {}, + |_| { + let mut ans = Vec::with_capacity(ITER); + for idx in 0..ITER { + let s = ArcIntern::::from_ref(&vals[idx % RANGE]); + + ans.push(s); + } + }, + criterion::BatchSize::PerIteration, + ); + }); + + group.bench_function(BenchmarkId::new("String", "short-from_str"), |b| { + b.iter_batched( + || {}, + |_| { + let mut ans = Vec::with_capacity(ITER); for idx in 0..ITER { - let s = ArcIntern::::new(format!("short-{}", idx % RANGE)); + let s = ArcIntern::::from_str(&vals[idx % RANGE]); + ans.push(s); } }, @@ -30,18 +61,18 @@ fn bench_get_container(c: &mut Criterion) { }); group.finish(); + let new_vals: Vec<_> = (0..RANGE) + .map(|idx| NewType(format!("short-{}", idx))) + .collect(); 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", "short"), |b| { b.iter_batched( || {}, |_| { - let mut ans = Vec::with_capacity(RANGE); + let mut ans = Vec::with_capacity(ITER); for idx in 0..ITER { - let s = ArcIntern::>::new(NewType(format!( - "short-{}", - idx % RANGE - ))); + let s = ArcIntern::>::new(new_vals[idx % RANGE].clone()); ans.push(s); } }, @@ -54,7 +85,7 @@ fn bench_get_container(c: &mut Criterion) { b.iter_batched( || {}, |_| { - let mut ans = Vec::with_capacity(RANGE); + let mut ans = Vec::with_capacity(ITER); for idx in 0..ITER { let s = ArcIntern::::new(idx % RANGE); ans.push(s); @@ -68,7 +99,7 @@ fn bench_get_container(c: &mut Criterion) { b.iter_batched( || {}, |_| { - let mut ans = Vec::with_capacity(RANGE); + let mut ans = Vec::with_capacity(ITER); for idx in 0..ITER { let s = ArcIntern::>::new(NewType(idx % RANGE)); ans.push(s); diff --git a/src/arc.rs b/src/arc.rs index 3f23f25..eb9ff59 100644 --- a/src/arc.rs +++ b/src/arc.rs @@ -123,6 +123,14 @@ impl Borrow> for BoxRefCount { &self.0 } } + +impl Borrow for BoxRefCount { + #[inline(always)] + fn borrow(&self) -> &str { + &self.0.data.borrow() + } +} + impl Deref for BoxRefCount { type Target = T; #[inline(always)] @@ -262,6 +270,60 @@ impl ArcIntern { } } +/// internal trait that allows us to specialize the `Borrow` trait for `str` +/// avoid the need to create an owned value first. +pub trait BorrowStr: Borrow {} + +impl BorrowStr for String {} + +/// BorrowStr specialization +impl ArcIntern { + /// Intern a value from a reference with atomic reference counting. + /// + /// this is a fast-path for str, as it avoids the need to create owned + /// value first. + pub fn from_str>(val: Q) -> ArcIntern + where + T: BorrowStr + for<'a> From<&'a str>, + { + // No reference only fast-path as + // the trait `std::borrow::Borrow` is not implemented for `Arc` + Self::new_from_str(val.as_ref()) + } + + /// Intern a value from a reference with atomic reference counting. + /// + /// If this value has not previously been + /// interned, then `new` will allocate a spot for the value on the + /// heap and generate that value using `T::from(val)`. + fn new_from_str<'a>(val: &'a str) -> ArcIntern + where + T: BorrowStr + From<&'a str>, + { + let m = Self::get_container(); + if let Some(b) = m.get(val) { + let b = b.key(); + // First increment the count. We are holding the write mutex here. + // Has to be the write mutex to avoid a race + let oldval = b.0.count.fetch_add(1, Ordering::SeqCst); + if oldval != 0 { + // we can only use this value if the value is not about to be freed + return ArcIntern { + pointer: std::ptr::NonNull::from(b.0.borrow()), + }; + } else { + // we have encountered a race condition here. + // we will just wait for the object to finish + // being freed. + b.0.count.fetch_sub(1, Ordering::SeqCst); + } + } + + // start over with the new value + Self::new(val.into()) + } +} + impl Clone for ArcIntern { fn clone(&self) -> Self { // First increment the count. Using a relaxed ordering is @@ -626,3 +688,14 @@ fn test_shrink_to_fit() { ArcIntern::::shrink_to_fit(); assert_eq!(0, ArcIntern::::get_container().capacity()); } + +#[test] +fn test_from_str() { + let x = ArcIntern::new("hello".to_string()); + let y = ArcIntern::::from_str("world"); + assert_ne!(x, y); + assert_eq!(x, ArcIntern::from_ref("hello")); + assert_eq!(x, ArcIntern::from_str("hello")); + assert_eq!(y, ArcIntern::from_ref("world")); + assert_eq!(&*x, "hello"); +}