Skip to content

Commit 551aece

Browse files
committed
refactor: improve memory usage
1 parent dbcc4ee commit 551aece

File tree

8 files changed

+432
-343
lines changed

8 files changed

+432
-343
lines changed

src/cache/entry.rs

Lines changed: 154 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,211 @@
11
use std::{
22
any::{Any, TypeId},
3+
mem::MaybeUninit,
34
sync::{
45
Arc,
5-
atomic::{AtomicBool, AtomicU32, Ordering}
6-
}
6+
atomic::{AtomicU8, AtomicU32, AtomicU64, Ordering}
7+
},
8+
time::Duration
79
};
810

911
use parking_lot::RwLock;
1012

1113
use crate::{
1214
error::MismatchedTypeError,
1315
fetcher::Fetcher,
14-
options::Options,
16+
options::StoredOptions,
1517
revalidate::RevalidateIntent,
1618
runtime::Runtime,
17-
util::{AtomicInstant, Instant, TaskSlot}
19+
util::{AtomicBitwise, Instant, TaskSlot}
1820
};
1921

22+
#[repr(transparent)]
23+
pub struct CacheEntryStatus(AtomicU8);
24+
25+
impl CacheEntryStatus {
26+
pub const HAS_DATA: u8 = 1 << 0;
27+
pub const HAS_ERROR: u8 = 1 << 1;
28+
29+
pub const LOADING: u8 = 1 << 2;
30+
pub const VALIDATING: u8 = 1 << 3;
31+
32+
pub const ALIVE: u8 = 1 << 4;
33+
pub const USED_THIS_PASS: u8 = 1 << 5;
34+
35+
pub fn new() -> Self {
36+
CacheEntryStatus(AtomicU8::new(0))
37+
}
38+
39+
pub fn load(&self, ordering: Ordering) -> u8 {
40+
self.0.load(ordering)
41+
}
42+
43+
pub fn set(&self, bits: u8, ordering: Ordering) -> bool {
44+
self.0.bits_set(bits, ordering)
45+
}
46+
47+
pub fn get(&self, bits: u8, ordering: Ordering) -> bool {
48+
self.0.bits_get(bits, ordering)
49+
}
50+
51+
pub fn clear(&self, bits: u8, ordering: Ordering) -> bool {
52+
self.0.bits_clear(bits, ordering)
53+
}
54+
}
55+
2056
pub struct CacheEntry<F: Fetcher, R: Runtime> {
21-
pub key: F::Key,
22-
data: Option<CacheEntryData>,
23-
pub error: Option<Arc<F::Error>>,
24-
pub loading: bool,
25-
pub validating: bool,
26-
27-
pub revalidate_intent: RevalidateIntent,
28-
pub used_this_pass: AtomicBool,
29-
pub alive: AtomicBool,
30-
pub last_draw_time: AtomicInstant,
31-
pub last_request_time: Option<Instant>,
57+
key: F::Key,
58+
59+
pub(crate) retry_count: AtomicU8,
60+
status: CacheEntryStatus,
61+
revalidate_intent: RevalidateIntent,
62+
data: MaybeUninit<CacheEntryData>,
63+
error: MaybeUninit<Arc<F::Error>>,
64+
65+
base_time: Instant,
66+
// offset from base time in nanos (up to ~584 years for 64 bits)
67+
last_draw_time_offset: AtomicU64,
68+
// offset from base time in nanos where u64::MAX is None, i.e. no request has been made
69+
last_request_time_offset: AtomicU64,
3270

3371
pub fetch_task: TaskSlot<R>,
3472
pub refresh_task: TaskSlot<R>,
35-
pub(crate) retry_count: AtomicU32,
3673
pub retry_task: TaskSlot<R>,
3774

38-
pub(crate) persisted_instances: AtomicU32,
39-
pub options: RwLock<Options<(), F>>
75+
pub(crate) strong_count: AtomicU32,
76+
pub options: RwLock<StoredOptions>
4077
}
4178

4279
impl<F: Fetcher, R: Runtime> CacheEntry<F, R> {
4380
pub fn new(runtime: R, key: F::Key) -> Self {
4481
Self {
4582
key,
46-
data: None,
47-
error: None,
48-
loading: false,
49-
validating: false,
5083

84+
retry_count: AtomicU8::new(0),
85+
status: CacheEntryStatus::new(),
5186
revalidate_intent: RevalidateIntent::default(),
52-
used_this_pass: AtomicBool::new(false),
53-
alive: AtomicBool::new(false),
54-
last_draw_time: AtomicInstant::now(),
55-
last_request_time: None,
87+
data: MaybeUninit::uninit(),
88+
error: MaybeUninit::uninit(),
89+
90+
base_time: Instant::now(),
91+
last_draw_time_offset: AtomicU64::new(0),
92+
last_request_time_offset: AtomicU64::new(u64::MAX),
5693

5794
fetch_task: TaskSlot::new(runtime.clone()),
5895
refresh_task: TaskSlot::new(runtime.clone()),
59-
retry_count: AtomicU32::new(0),
6096
retry_task: TaskSlot::new(runtime),
6197

62-
persisted_instances: AtomicU32::new(0),
63-
options: RwLock::new(Options::default())
98+
strong_count: AtomicU32::new(0),
99+
options: RwLock::new(StoredOptions::default())
64100
}
65101
}
66102

67103
pub fn data<T: Send + Sync + 'static>(&self) -> Option<Result<Arc<F::Response<T>>, MismatchedTypeError>> {
68-
self.data.as_ref().map(|data| match Arc::downcast(data.value.clone()) {
69-
Ok(x) => Ok(x),
70-
Err(_) => Err(MismatchedTypeError {
71-
contained_type: self.data.type_id(),
72-
wanted_type: TypeId::of::<T>(),
73-
74-
#[cfg(debug_assertions)]
75-
contained_type_name: data.type_name,
76-
#[cfg(debug_assertions)]
77-
wanted_type_name: std::any::type_name::<T>()
104+
if self.status.get(CacheEntryStatus::HAS_DATA, Ordering::Acquire) {
105+
let data = unsafe { self.data.assume_init_ref() };
106+
Some(match Arc::downcast(data.value.clone()) {
107+
Ok(x) => Ok(x),
108+
Err(_) => Err(MismatchedTypeError {
109+
contained_type: self.data.type_id(),
110+
wanted_type: TypeId::of::<T>(),
111+
112+
#[cfg(debug_assertions)]
113+
contained_type_name: data.type_name,
114+
#[cfg(debug_assertions)]
115+
wanted_type_name: std::any::type_name::<T>()
116+
})
78117
})
79-
})
118+
} else {
119+
None
120+
}
121+
}
122+
123+
pub fn error(&self) -> Option<&Arc<F::Error>> {
124+
if self.status.get(CacheEntryStatus::HAS_ERROR, Ordering::Acquire) {
125+
let err = unsafe { self.error.assume_init_ref() };
126+
Some(err)
127+
} else {
128+
None
129+
}
80130
}
81131

82132
#[inline]
83-
pub fn has_data(&self) -> bool {
84-
self.data.is_some()
133+
pub fn revalidate_intent(&self) -> &RevalidateIntent {
134+
&self.revalidate_intent
85135
}
86136

87-
pub fn insert<T: Send + Sync + 'static>(&mut self, data: Arc<F::Response<T>>) {
137+
#[inline]
138+
pub fn status(&self) -> &CacheEntryStatus {
139+
&self.status
140+
}
141+
142+
#[inline]
143+
pub fn key(&self) -> &F::Key {
144+
&self.key
145+
}
146+
147+
pub fn insert<T: Send + Sync + 'static>(&mut self, data: Arc<F::Response<T>>) -> Option<CacheEntryData> {
88148
self.insert_untyped(
89149
data as _,
90150
#[cfg(debug_assertions)]
91151
std::any::type_name::<T>()
92-
);
152+
)
93153
}
94154

95-
pub fn insert_untyped(&mut self, data: Arc<dyn Any + Send + Sync>, #[cfg(debug_assertions)] type_name: &'static str) {
96-
self.loading = false;
97-
self.validating = false;
155+
pub fn insert_untyped(&mut self, data: Arc<dyn Any + Send + Sync>, #[cfg(debug_assertions)] type_name: &'static str) -> Option<CacheEntryData> {
156+
self.status
157+
.clear(CacheEntryStatus::LOADING | CacheEntryStatus::VALIDATING, Ordering::Relaxed); // we have mut
98158

99-
self.data = Some(CacheEntryData {
159+
let old_data = if self.status.set(CacheEntryStatus::HAS_DATA, Ordering::Relaxed) {
160+
Some(unsafe { self.data.assume_init_read() })
161+
} else {
162+
None
163+
};
164+
self.data.write(CacheEntryData {
100165
value: data,
101166
#[cfg(debug_assertions)]
102167
type_name
103168
});
104169

105-
self.error.take();
106-
self.retry_count.store(0, Ordering::Release);
170+
if self.status.clear(CacheEntryStatus::HAS_ERROR, Ordering::Relaxed) {
171+
unsafe { self.error.assume_init_drop() };
172+
}
173+
174+
self.retry_count.store(0, Ordering::Relaxed);
175+
self.last_request_time_offset
176+
.store(instant_as_offset(&self.base_time, Instant::now()), Ordering::Relaxed);
107177

108-
self.last_request_time.replace(Instant::now());
178+
old_data
109179
}
110180

111181
pub fn insert_error(&mut self, error: Arc<F::Error>) {
112-
self.loading = false;
113-
self.validating = false;
182+
self.status
183+
.clear(CacheEntryStatus::LOADING | CacheEntryStatus::VALIDATING, Ordering::Relaxed); // we have mut
114184

115-
self.error = Some(error);
116-
self.last_request_time.replace(Instant::now());
117-
}
185+
if self.status.set(CacheEntryStatus::HAS_ERROR, Ordering::Relaxed) {
186+
unsafe { self.error.assume_init_drop() };
187+
}
188+
self.error.write(error);
118189

119-
pub fn swap<T: Send + Sync + 'static>(&mut self, data: Arc<F::Response<T>>) -> Option<CacheEntryData> {
120-
let old_data = self.data.take();
121-
self.insert_untyped(
122-
data,
123-
#[cfg(debug_assertions)]
124-
std::any::type_name::<T>()
125-
);
126-
old_data
190+
self.last_request_time_offset
191+
.store(instant_as_offset(&self.base_time, Instant::now()), Ordering::Relaxed);
127192
}
128193

129194
pub fn mark_used(&self) {
130-
self.last_draw_time.store_now(Ordering::Release);
131-
self.used_this_pass.store(true, Ordering::Release);
195+
self.last_draw_time_offset
196+
.store(instant_as_offset(&self.base_time, Instant::now()), Ordering::Release);
197+
self.status.set(CacheEntryStatus::USED_THIS_PASS, Ordering::Release);
198+
}
199+
200+
pub fn last_request_time(&self, order: Ordering) -> Option<Instant> {
201+
match self.last_request_time_offset.load(order) {
202+
u64::MAX => None,
203+
offs => Some(instant_from_offset(&self.base_time, offs))
204+
}
205+
}
206+
207+
pub fn last_draw_time(&self, order: Ordering) -> Instant {
208+
instant_from_offset(&self.base_time, self.last_draw_time_offset.load(order))
132209
}
133210
}
134211

@@ -137,3 +214,14 @@ pub struct CacheEntryData {
137214
#[cfg(debug_assertions)]
138215
pub type_name: &'static str
139216
}
217+
218+
fn instant_as_offset(base: &Instant, new_value: Instant) -> u64 {
219+
let offset = new_value - *base;
220+
offset.as_secs() * 1_000_000_000 + u64::from(offset.subsec_nanos())
221+
}
222+
223+
fn instant_from_offset(base: &Instant, offset_nanos: u64) -> Instant {
224+
let secs = offset_nanos / 1_000_000_000;
225+
let subsec_nanos = (offset_nanos % 1_000_000_000) as u32;
226+
*base + Duration::new(secs, subsec_nanos)
227+
}

src/cache/mod.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ use parking_lot::{RwLock, RwLockUpgradableReadGuard};
44
use slotmap::SlotMap;
55

66
mod entry;
7-
mod slot;
8-
pub(crate) use self::{entry::CacheEntry, slot::CacheSlot};
7+
pub(crate) use self::entry::{CacheEntry, CacheEntryStatus};
98
use crate::{fetcher::Fetcher, runtime::Runtime};
109

10+
slotmap::new_key_type! {
11+
pub struct CacheSlot;
12+
}
13+
1114
pub struct Cache<F: Fetcher, R: Runtime> {
1215
runtime: R,
13-
pub(super) key_to_slot: RwLock<HashMap<F::Key, CacheSlot<F>>>,
14-
states: RwLock<SlotMap<CacheSlot<F>, CacheEntry<F, R>>>
16+
key_to_slot: RwLock<HashMap<F::Key, CacheSlot>>,
17+
states: RwLock<SlotMap<CacheSlot, CacheEntry<F, R>>>
1518
}
1619

1720
impl<F: Fetcher, R: Runtime> Cache<F, R> {
@@ -23,7 +26,7 @@ impl<F: Fetcher, R: Runtime> Cache<F, R> {
2326
}
2427
}
2528

26-
pub fn get<K>(&self, key: &K) -> Option<CacheSlot<F>>
29+
pub fn get<K>(&self, key: &K) -> Option<CacheSlot>
2730
where
2831
K: Hash + Eq + ?Sized,
2932
F::Key: Borrow<K>
@@ -32,7 +35,7 @@ impl<F: Fetcher, R: Runtime> Cache<F, R> {
3235
key_to_slot.get(key).copied()
3336
}
3437

35-
pub fn get_or_create<K>(&self, key: &K) -> CacheSlot<F>
38+
pub fn get_or_create<K>(&self, key: &K) -> CacheSlot
3639
where
3740
K: Hash + Eq + ?Sized,
3841
F::Key: Borrow<K> + for<'k> From<&'k K>
@@ -52,28 +55,37 @@ impl<F: Fetcher, R: Runtime> Cache<F, R> {
5255
}
5356
}
5457

58+
pub(crate) fn retain<I: FnMut(CacheSlot, &mut CacheEntry<F, R>) -> bool>(&self, mut cb: I) {
59+
let mut key_to_slot = self.key_to_slot.write();
60+
let mut states = self.states.write();
61+
states.retain(|slot, entry| {
62+
if !cb(slot, entry) {
63+
key_to_slot.remove(entry.key());
64+
false
65+
} else {
66+
true
67+
}
68+
})
69+
}
70+
5571
pub fn states(&self) -> StateAccessor<'_, F, R> {
5672
StateAccessor { inner: self.states.upgradable_read() }
5773
}
5874
}
5975

6076
pub struct StateAccessor<'c, F: Fetcher, R: Runtime> {
61-
inner: RwLockUpgradableReadGuard<'c, SlotMap<CacheSlot<F>, CacheEntry<F, R>>>
77+
inner: RwLockUpgradableReadGuard<'c, SlotMap<CacheSlot, CacheEntry<F, R>>>
6278
}
6379

6480
impl<F: Fetcher, R: Runtime> StateAccessor<'_, F, R> {
65-
pub fn get(&self, slot: CacheSlot<F>) -> Option<&CacheEntry<F, R>> {
81+
pub fn get(&self, slot: CacheSlot) -> Option<&CacheEntry<F, R>> {
6682
self.inner.get(slot)
6783
}
6884

69-
pub fn mutate<M, T>(&mut self, slot: CacheSlot<F>, mutator: M) -> Option<T>
85+
pub fn mutate<M, T>(&mut self, slot: CacheSlot, mutator: M) -> Option<T>
7086
where
7187
M: FnOnce(&mut CacheEntry<F, R>) -> T
7288
{
7389
self.inner.with_upgraded(|states| states.get_mut(slot).map(mutator))
7490
}
75-
76-
pub fn retain<I: FnMut(CacheSlot<F>, &mut CacheEntry<F, R>) -> bool>(&mut self, cb: I) {
77-
self.inner.with_upgraded(|states| states.retain(cb));
78-
}
7991
}

0 commit comments

Comments
 (0)