Skip to content

Commit 1f19805

Browse files
authored
Primary caching 11: cache stats and integration with memory panel (#4773)
The primary cache now tracks memory statistics and display them in the memory panel. This immediately highlights a very stupid thing that the cache does: missing optional components that have been turned into streams of default values by the `ArchetypeView` are materialized as such :man_facepalming: - #4779 https://github.com/rerun-io/rerun/assets/2910679/876b264a-3f77-4d91-934e-aa8897bb32fe - Fixes #4730 --- Part of the primary caching series of PR (index search, joins, deserialization): - #4592 - #4593 - #4659 - #4680 - #4681 - #4698 - #4711 - #4712 - #4721 - #4726 - #4773 - #4784 - #4785 - #4793 - #4800
1 parent 68de772 commit 1f19805

File tree

11 files changed

+421
-62
lines changed

11 files changed

+421
-62
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/re_log_types/src/time_point/time_int.rs

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ pub struct TimeInt(pub(crate) i64);
1111

1212
impl nohash_hasher::IsEnabled for TimeInt {}
1313

14+
impl re_types_core::SizeBytes for TimeInt {
15+
#[inline]
16+
fn heap_size_bytes(&self) -> u64 {
17+
0
18+
}
19+
}
20+
1421
impl TimeInt {
1522
/// The beginning of time.
1623
///

crates/re_memory/src/memory_history.rs

+32-13
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub struct MemoryHistory {
2525
/// Bytes used by the datastore according to its own accounting.
2626
pub counted_store: History<i64>,
2727

28+
/// Bytes used by the primary caches according to their own accounting.
29+
pub counted_primary_caches: History<i64>,
30+
2831
/// Bytes used by the blueprint store according to its own accounting.
2932
pub counted_blueprint: History<i64>,
3033
}
@@ -38,6 +41,7 @@ impl Default for MemoryHistory {
3841
counted: History::new(0..max_elems, max_seconds),
3942
counted_gpu: History::new(0..max_elems, max_seconds),
4043
counted_store: History::new(0..max_elems, max_seconds),
44+
counted_primary_caches: History::new(0..max_elems, max_seconds),
4145
counted_blueprint: History::new(0..max_elems, max_seconds),
4246
}
4347
}
@@ -50,39 +54,54 @@ impl MemoryHistory {
5054
counted,
5155
counted_gpu,
5256
counted_store,
57+
counted_primary_caches,
5358
counted_blueprint,
5459
} = self;
5560
resident.is_empty()
5661
&& counted.is_empty()
5762
&& counted_gpu.is_empty()
5863
&& counted_store.is_empty()
64+
&& counted_primary_caches.is_empty()
5965
&& counted_blueprint.is_empty()
6066
}
6167

6268
/// Add data to history
6369
pub fn capture(
6470
&mut self,
65-
counted_gpu: Option<i64>,
66-
counted_store: Option<i64>,
67-
counted_blueprint: Option<i64>,
71+
updated_counted_gpu: Option<i64>,
72+
updated_counted_store: Option<i64>,
73+
updated_counted_primary_caches: Option<i64>,
74+
updated_counted_blueprint: Option<i64>,
6875
) {
6976
let mem_use = crate::MemoryUse::capture();
7077
let now = crate::util::sec_since_start();
7178

72-
if let Some(resident) = mem_use.resident {
73-
self.resident.add(now, resident);
79+
let Self {
80+
resident,
81+
counted,
82+
counted_gpu,
83+
counted_store,
84+
counted_primary_caches,
85+
counted_blueprint,
86+
} = self;
87+
88+
if let Some(updated_resident) = mem_use.resident {
89+
resident.add(now, updated_resident);
90+
}
91+
if let Some(updated_counted) = mem_use.counted {
92+
counted.add(now, updated_counted);
7493
}
75-
if let Some(counted) = mem_use.counted {
76-
self.counted.add(now, counted);
94+
if let Some(updated_counted_gpu) = updated_counted_gpu {
95+
counted_gpu.add(now, updated_counted_gpu);
7796
}
78-
if let Some(counted_gpu) = counted_gpu {
79-
self.counted_gpu.add(now, counted_gpu);
97+
if let Some(updated_counted_store) = updated_counted_store {
98+
counted_store.add(now, updated_counted_store);
8099
}
81-
if let Some(counted_store) = counted_store {
82-
self.counted_store.add(now, counted_store);
100+
if let Some(updated_counted_primary_caches) = updated_counted_primary_caches {
101+
counted_primary_caches.add(now, updated_counted_primary_caches);
83102
}
84-
if let Some(counted_blueprint) = counted_blueprint {
85-
self.counted_blueprint.add(now, counted_blueprint);
103+
if let Some(updated_counted_blueprint) = updated_counted_blueprint {
104+
counted_blueprint.add(now, updated_counted_blueprint);
86105
}
87106
}
88107
}

crates/re_query_cache/src/cache.rs

+98-32
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use re_data_store::{
1414
};
1515
use re_log_types::{EntityPath, RowId, StoreId, TimeInt, Timeline};
1616
use re_query::ArchetypeView;
17-
use re_types_core::{components::InstanceKey, Archetype, ArchetypeName, Component, ComponentName};
17+
use re_types_core::{
18+
components::InstanceKey, Archetype, ArchetypeName, Component, ComponentName, SizeBytes as _,
19+
};
1820

1921
use crate::{ErasedFlatVecDeque, FlatVecDeque};
2022

@@ -49,10 +51,8 @@ static CACHES: Lazy<StoreSubscriberHandle> =
4951
Lazy::new(|| re_data_store::DataStore::register_subscriber(Box::<Caches>::default()));
5052

5153
/// Maintains the top-level cache mappings.
52-
//
53-
// TODO(#4730): SizeBytes support + size stats + mem panel
5454
#[derive(Default)]
55-
pub struct Caches(RwLock<HashMap<CacheKey, CachesPerArchetype>>);
55+
pub struct Caches(pub(crate) RwLock<HashMap<CacheKey, CachesPerArchetype>>);
5656

5757
#[derive(Default)]
5858
pub struct CachesPerArchetype {
@@ -65,7 +65,7 @@ pub struct CachesPerArchetype {
6565
//
6666
// TODO(cmc): At some point we should probably just store the PoV and optional components rather
6767
// than an `ArchetypeName`: the query system doesn't care about archetypes.
68-
latest_at_per_archetype: RwLock<HashMap<ArchetypeName, Arc<RwLock<LatestAtCache>>>>,
68+
pub(crate) latest_at_per_archetype: RwLock<HashMap<ArchetypeName, Arc<RwLock<LatestAtCache>>>>,
6969

7070
/// Everything greater than or equal to this timestamp has been asynchronously invalidated.
7171
///
@@ -116,8 +116,8 @@ impl Caches {
116116
re_data_store::DataStore::with_subscriber_once(*CACHES, move |caches: &Caches| {
117117
let mut caches = caches.0.write();
118118

119-
let caches_per_archetype = caches.entry(key).or_default();
120-
caches_per_archetype.handle_pending_invalidation();
119+
let caches_per_archetype = caches.entry(key.clone()).or_default();
120+
caches_per_archetype.handle_pending_invalidation(&key);
121121

122122
let mut latest_at_per_archetype =
123123
caches_per_archetype.latest_at_per_archetype.write();
@@ -133,6 +133,12 @@ impl Caches {
133133
let mut cache = cache.write();
134134
f(&mut cache)
135135
}
136+
137+
#[inline]
138+
pub(crate) fn with<F: FnMut(&Caches) -> R, R>(f: F) -> R {
139+
// NOTE: downcasting cannot fail, this is our own private handle.
140+
re_data_store::DataStore::with_subscriber(*CACHES, f).unwrap()
141+
}
136142
}
137143

138144
/// Uniquely identifies cached query results in the [`Caches`].
@@ -264,7 +270,7 @@ impl CachesPerArchetype {
264270
///
265271
/// Invalidation is deferred to query time because it is far more efficient that way: the frame
266272
/// time effectively behaves as a natural micro-batching mechanism.
267-
fn handle_pending_invalidation(&mut self) {
273+
fn handle_pending_invalidation(&mut self, key: &CacheKey) {
268274
let pending_timeless_invalidation = self.pending_timeless_invalidation;
269275
let pending_timeful_invalidation = self.pending_timeful_invalidation.is_some();
270276

@@ -281,15 +287,39 @@ impl CachesPerArchetype {
281287
latest_at_cache.timeless = None;
282288
}
283289

290+
let mut removed_bytes = 0u64;
284291
if let Some(min_time) = self.pending_timeful_invalidation {
285292
latest_at_cache
286293
.per_query_time
287294
.retain(|&query_time, _| query_time < min_time);
288295

289-
latest_at_cache
290-
.per_data_time
291-
.retain(|&data_time, _| data_time < min_time);
296+
latest_at_cache.per_data_time.retain(|&data_time, bucket| {
297+
if data_time < min_time {
298+
return true;
299+
}
300+
301+
// Only if that bucket is about to be dropped.
302+
if Arc::strong_count(bucket) == 1 {
303+
removed_bytes += bucket.read().total_size_bytes;
304+
}
305+
306+
false
307+
});
292308
}
309+
310+
latest_at_cache.total_size_bytes = latest_at_cache
311+
.total_size_bytes
312+
.checked_sub(removed_bytes)
313+
.unwrap_or_else(|| {
314+
re_log::debug!(
315+
store_id = %key.store_id,
316+
entity_path = %key.entity_path,
317+
current = latest_at_cache.total_size_bytes,
318+
removed = removed_bytes,
319+
"book keeping underflowed"
320+
);
321+
u64::MIN
322+
});
293323
}
294324

295325
self.pending_timeful_invalidation = None;
@@ -328,9 +358,14 @@ pub struct CacheBucket {
328358
// TODO(#4733): Don't denormalize auto-generated instance keys.
329359
// TODO(#4734): Don't denormalize splatted values.
330360
pub(crate) components: BTreeMap<ComponentName, Box<dyn ErasedFlatVecDeque + Send + Sync>>,
361+
362+
/// The total size in bytes stored in this bucket.
363+
///
364+
/// Only used so we can decrement the global cache size when the last reference to a bucket
365+
/// gets dropped.
366+
pub(crate) total_size_bytes: u64,
331367
//
332368
// TODO(cmc): secondary cache
333-
// TODO(#4730): size stats: this requires codegen'ing SizeBytes for all components!
334369
}
335370

336371
impl CacheBucket {
@@ -387,12 +422,14 @@ macro_rules! impl_insert {
387422
#[doc = "Inserts the contents of the given [`ArchetypeView`], which are made of the specified"]
388423
#[doc = "`" $N "` point-of-view components and `" $M "` optional components, to the cache."]
389424
#[doc = ""]
425+
#[doc = "Returns the size in bytes of the data that was cached."]
426+
#[doc = ""]
390427
#[doc = "`query_time` must be the time of query, _not_ of the resulting data."]
391428
pub fn [<insert_pov$N _comp$M>]<A, $($pov,)+ $($comp),*>(
392429
&mut self,
393430
query_time: TimeInt,
394431
arch_view: &ArchetypeView<A>,
395-
) -> ::re_query::Result<()>
432+
) -> ::re_query::Result<u64>
396433
where
397434
A: Archetype,
398435
$($pov: Component + Send + Sync + 'static,)+
@@ -401,21 +438,31 @@ macro_rules! impl_insert {
401438
// NOTE: not `profile_function!` because we want them merged together.
402439
re_tracing::profile_scope!("CacheBucket::insert", format!("arch={} pov={} comp={}", A::name(), $N, $M));
403440

404-
let Self {
405-
data_times,
406-
pov_instance_keys,
407-
components: _,
408-
} = self;
409-
410441
let pov_row_id = arch_view.primary_row_id();
411-
let index = data_times.partition_point(|t| t < &(query_time, pov_row_id));
442+
let index = self.data_times.partition_point(|t| t < &(query_time, pov_row_id));
443+
444+
let mut added_size_bytes = 0u64;
445+
446+
self.data_times.insert(index, (query_time, pov_row_id));
447+
added_size_bytes += (query_time, pov_row_id).total_size_bytes();
448+
449+
{
450+
// The `FlatVecDeque` will have to collect the data one way or another: do it ourselves
451+
// instead, that way we can efficiently computes its size while we're at it.
452+
let added: FlatVecDeque<InstanceKey> = arch_view
453+
.iter_instance_keys()
454+
.collect::<VecDeque<InstanceKey>>()
455+
.into();
456+
added_size_bytes += added.total_size_bytes();
457+
self.pov_instance_keys.insert_deque(index, added);
458+
}
459+
460+
$(added_size_bytes += self.insert_component::<A, $pov>(index, arch_view)?;)+
461+
$(added_size_bytes += self.insert_component_opt::<A, $comp>(index, arch_view)?;)*
412462

413-
data_times.insert(index, (query_time, pov_row_id));
414-
pov_instance_keys.insert(index, arch_view.iter_instance_keys());
415-
$(self.insert_component::<A, $pov>(index, arch_view)?;)+
416-
$(self.insert_component_opt::<A, $comp>(index, arch_view)?;)*
463+
self.total_size_bytes += added_size_bytes;
417464

418-
Ok(())
465+
Ok(added_size_bytes)
419466
} }
420467
};
421468

@@ -436,7 +483,7 @@ impl CacheBucket {
436483
&mut self,
437484
query_time: TimeInt,
438485
arch_view: &ArchetypeView<A>,
439-
) -> ::re_query::Result<()>
486+
) -> ::re_query::Result<u64>
440487
where
441488
A: Archetype,
442489
R1: Component + Send + Sync + 'static,
@@ -453,42 +500,58 @@ impl CacheBucket {
453500
&mut self,
454501
at: usize,
455502
arch_view: &ArchetypeView<A>,
456-
) -> re_query::Result<()> {
503+
) -> re_query::Result<u64> {
457504
re_tracing::profile_function!(C::name());
458505

459506
let data = self
460507
.components
461508
.entry(C::name())
462509
.or_insert_with(|| Box::new(FlatVecDeque::<C>::new()));
463510

511+
// The `FlatVecDeque` will have to collect the data one way or another: do it ourselves
512+
// instead, that way we can efficiently computes its size while we're at it.
513+
let added: FlatVecDeque<C> = arch_view
514+
.iter_required_component::<C>()?
515+
.collect::<VecDeque<C>>()
516+
.into();
517+
let added_size_bytes = added.total_size_bytes();
518+
464519
// NOTE: downcast cannot fail, we create it just above.
465520
let data = data.as_any_mut().downcast_mut::<FlatVecDeque<C>>().unwrap();
466-
data.insert(at, arch_view.iter_required_component::<C>()?);
521+
data.insert_deque(at, added);
467522

468-
Ok(())
523+
Ok(added_size_bytes)
469524
}
470525

471526
#[inline]
472527
fn insert_component_opt<A: Archetype, C: Component + Send + Sync + 'static>(
473528
&mut self,
474529
at: usize,
475530
arch_view: &ArchetypeView<A>,
476-
) -> re_query::Result<()> {
531+
) -> re_query::Result<u64> {
477532
re_tracing::profile_function!(C::name());
478533

479534
let data = self
480535
.components
481536
.entry(C::name())
482537
.or_insert_with(|| Box::new(FlatVecDeque::<Option<C>>::new()));
483538

539+
// The `FlatVecDeque` will have to collect the data one way or another: do it ourselves
540+
// instead, that way we can efficiently computes its size while we're at it.
541+
let added: FlatVecDeque<Option<C>> = arch_view
542+
.iter_optional_component::<C>()?
543+
.collect::<VecDeque<Option<C>>>()
544+
.into();
545+
let added_size_bytes = added.total_size_bytes();
546+
484547
// NOTE: downcast cannot fail, we create it just above.
485548
let data = data
486549
.as_any_mut()
487550
.downcast_mut::<FlatVecDeque<Option<C>>>()
488551
.unwrap();
489-
data.insert(at, arch_view.iter_optional_component::<C>()?);
552+
data.insert_deque(at, added);
490553

491-
Ok(())
554+
Ok(added_size_bytes)
492555
}
493556
}
494557

@@ -526,4 +589,7 @@ pub struct LatestAtCache {
526589
// NOTE: Lives separately so we don't pay the extra `Option` cost in the much more common
527590
// timeful case.
528591
pub timeless: Option<CacheBucket>,
592+
593+
/// Total size of the data stored in this cache in bytes.
594+
pub total_size_bytes: u64,
529595
}

0 commit comments

Comments
 (0)