Skip to content

Commit af07dcc

Browse files
authored
feat: skip alocation for arc_t inputs (#77)
1 parent a9178dd commit af07dcc

File tree

4 files changed

+130
-5
lines changed

4 files changed

+130
-5
lines changed

src/flavors/arc_swap.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ pub struct Slot<T> {
88
}
99

1010
impl<T> SwapSlot<T> for Slot<T> {
11-
fn store(&self, item: T) {
12-
self.shared.store(Some(Arc::new(item)))
11+
fn store(&self, item: impl Into<Arc<T>>) {
12+
self.shared.store(Some(item.into()));
1313
}
1414

1515
fn load(&self) -> Option<Arc<T>> {
@@ -70,3 +70,46 @@ mod test {
7070
assert_eq!(Arc::strong_count(&arc.unwrap()), 2)
7171
}
7272
}
73+
74+
#[cfg(test)]
75+
mod allocation_tests {
76+
use crate::flavors::allocation_tests::{allocs_current_thread, reset_allocs_current_thread};
77+
78+
use super::*;
79+
80+
#[test]
81+
fn store_with_arc_does_not_allocate_new_arc() {
82+
let slot = Slot::<u32>::none();
83+
let arc = Arc::new(123u32);
84+
85+
// Ignore allocations from constructing `slot` and `arc`.
86+
reset_allocs_current_thread();
87+
88+
// This should only move / clone the Arc; no new heap allocation for T.
89+
slot.store(arc.clone());
90+
91+
// Might still be 0 or some tiny number depending on RwLock internals,
92+
// but definitely shouldn't be "one Arc allocation vs another" difference.
93+
let after = allocs_current_thread();
94+
assert_eq!(
95+
after, 0,
96+
"expected no additional allocations when storing an Arc"
97+
);
98+
}
99+
100+
#[test]
101+
fn store_with_value_allocates_arc() {
102+
let slot = Slot::<u32>::none();
103+
104+
reset_allocs_current_thread();
105+
106+
// This goes through `impl From<T> for Arc<T>` and must allocate.
107+
slot.store(5u32);
108+
109+
let after = allocs_current_thread();
110+
assert!(
111+
after == 1,
112+
"expected at least one allocation when storing a bare value T"
113+
);
114+
}
115+
}

src/flavors/mod.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,42 @@ pub mod arc_swap;
33

44
#[cfg(feature = "rwlock")]
55
pub mod rw_lock;
6+
7+
#[cfg(test)]
8+
mod allocation_tests {
9+
use std::alloc::{GlobalAlloc, Layout, System};
10+
use std::cell::Cell;
11+
12+
struct CountingAlloc;
13+
14+
#[global_allocator]
15+
static GLOBAL: CountingAlloc = CountingAlloc;
16+
17+
thread_local! {
18+
// Each OS thread gets its own counter.
19+
static ALLOCS_THIS_THREAD: Cell<usize> = Cell::new(0);
20+
}
21+
22+
unsafe impl GlobalAlloc for CountingAlloc {
23+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
24+
let ptr = System.alloc(layout);
25+
if !ptr.is_null() {
26+
// Only bump the counter for the *current* thread.
27+
ALLOCS_THIS_THREAD.with(|c| c.set(c.get() + 1));
28+
}
29+
ptr
30+
}
31+
32+
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
33+
System.dealloc(ptr, layout);
34+
}
35+
}
36+
37+
pub(crate) fn reset_allocs_current_thread() {
38+
ALLOCS_THIS_THREAD.with(|c| c.set(0));
39+
}
40+
41+
pub(crate) fn allocs_current_thread() -> usize {
42+
ALLOCS_THIS_THREAD.with(|c| c.get())
43+
}
44+
}

src/flavors/rw_lock.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ pub struct Slot<T> {
77
}
88

99
impl<T> SwapSlot<T> for Slot<T> {
10-
fn store(&self, item: T) {
11-
*self.lock.write().unwrap() = Some(Arc::new(item));
10+
fn store(&self, item: impl Into<Arc<T>>) {
11+
*self.lock.write().unwrap() = Some(item.into());
1212
}
1313

1414
fn load(&self) -> Option<Arc<T>> {
@@ -69,3 +69,46 @@ mod test {
6969
assert_eq!(Arc::strong_count(&arc.unwrap()), 2)
7070
}
7171
}
72+
73+
#[cfg(test)]
74+
mod allocation_tests {
75+
use crate::flavors::allocation_tests::{allocs_current_thread, reset_allocs_current_thread};
76+
77+
use super::*;
78+
79+
#[test]
80+
fn store_with_arc_does_not_allocate_new_arc() {
81+
let slot = Slot::<u32>::none();
82+
let arc = Arc::new(123u32);
83+
84+
// Ignore allocations from constructing `slot` and `arc`.
85+
reset_allocs_current_thread();
86+
87+
// This should only move / clone the Arc; no new heap allocation for T.
88+
slot.store(arc.clone());
89+
90+
// Might still be 0 or some tiny number depending on RwLock internals,
91+
// but definitely shouldn't be "one Arc allocation vs another" difference.
92+
let after = allocs_current_thread();
93+
assert_eq!(
94+
after, 0,
95+
"expected no additional allocations when storing an Arc"
96+
);
97+
}
98+
99+
#[test]
100+
fn store_with_value_allocates_arc() {
101+
let slot = Slot::<u32>::none();
102+
103+
reset_allocs_current_thread();
104+
105+
// This goes through `impl From<T> for Arc<T>` and must allocate.
106+
slot.store(5u32);
107+
108+
let after = allocs_current_thread();
109+
assert!(
110+
after == 1,
111+
"expected at least one allocation when storing a bare value T"
112+
);
113+
}
114+
}

src/swap_slot.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::Arc;
44
pub trait SwapSlot<T> {
55
/// Creates a new Arc around item and stores it,
66
/// dropping the previously held item's Arc.
7-
fn store(&self, item: T);
7+
fn store(&self, item: impl Into<Arc<T>>);
88

99
/// Returns a clone of the held Arc,
1010
/// incrementing the ref count atomically

0 commit comments

Comments
 (0)