Skip to content

Commit f569223

Browse files
authored
Primary caching 9: timeless latest-at support (#4721)
Introduces a dedicated cache bucket for timeless data and properly forwards the information through all APIs downstream. --- 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 ccfd21a commit f569223

File tree

6 files changed

+169
-128
lines changed

6 files changed

+169
-128
lines changed

crates/re_log_types/src/time_point/time_int.rs

-5
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,6 @@ impl TimeInt {
6868
pub fn abs(&self) -> Self {
6969
Self(self.0.saturating_abs())
7070
}
71-
72-
#[inline]
73-
pub fn is_timeless(&self) -> bool {
74-
self == &Self::BEGINNING
75-
}
7671
}
7772

7873
impl From<i64> for TimeInt {

crates/re_query_cache/src/cache.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ static CACHES: Lazy<Caches> = Lazy::new(Caches::default);
4949
//
5050
// TODO(cmc): Store subscriber and cache invalidation.
5151
// TODO(#4730): SizeBytes support + size stats + mem panel
52-
// TODO(cmc): timeless caching support
5352
#[derive(Default)]
5453
pub struct Caches {
5554
latest_at: RwLock<HashMap<CacheKey, Arc<RwLock<LatestAtCache>>>>,
@@ -351,4 +350,13 @@ pub struct LatestAtCache {
351350
/// Due to how our latest-at semantics work, any number of queries at time `T+n` where `n >= 0`
352351
/// can result in a data time of `T`.
353352
pub per_data_time: BTreeMap<TimeInt, Arc<RwLock<CacheBucket>>>,
353+
354+
/// Dedicated bucket for timeless data, if any.
355+
///
356+
/// Query time and data time are one and the same in the timeless case, therefore we only need
357+
/// this one bucket.
358+
//
359+
// NOTE: Lives separately so we don't pay the extra `Option` cost in the much more common
360+
// timeful case.
361+
pub timeless: Option<CacheBucket>,
354362
}

crates/re_query_cache/src/query.rs

+77-45
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ where
6666
R1: Component + Send + Sync + 'static,
6767
F: FnMut(
6868
(
69-
(TimeInt, RowId),
69+
(Option<TimeInt>, RowId),
7070
MaybeCachedComponentData<'_, InstanceKey>,
7171
MaybeCachedComponentData<'_, R1>,
7272
),
@@ -93,7 +93,7 @@ macro_rules! impl_query_archetype {
9393
$($comp: Component + Send + Sync + 'static,)*
9494
F: FnMut(
9595
(
96-
(TimeInt, RowId),
96+
(Option<TimeInt>, RowId),
9797
MaybeCachedComponentData<'_, InstanceKey>,
9898
$(MaybeCachedComponentData<'_, $pov>,)+
9999
$(MaybeCachedComponentData<'_, Option<$comp>>,)*
@@ -107,7 +107,7 @@ macro_rules! impl_query_archetype {
107107
);
108108

109109

110-
let mut iter_results = |bucket: &crate::CacheBucket| -> crate::Result<()> {
110+
let mut iter_results = |timeless: bool, bucket: &crate::CacheBucket| -> crate::Result<()> {
111111
re_tracing::profile_scope!("iter");
112112

113113
let it = itertools::izip!(
@@ -117,9 +117,9 @@ macro_rules! impl_query_archetype {
117117
.ok_or_else(|| re_query::ComponentNotFoundError(<$pov>::name()))?,)+
118118
$(bucket.iter_component_opt::<$comp>()
119119
.ok_or_else(|| re_query::ComponentNotFoundError(<$comp>::name()))?,)*
120-
).map(|(time, instance_keys, $($pov,)+ $($comp,)*)| {
120+
).map(|((time, row_id), instance_keys, $($pov,)+ $($comp,)*)| {
121121
(
122-
*time,
122+
((!timeless).then_some(*time), *row_id),
123123
MaybeCachedComponentData::Cached(instance_keys),
124124
$(MaybeCachedComponentData::Cached($pov),)+
125125
$(MaybeCachedComponentData::Cached($comp),)*
@@ -133,68 +133,102 @@ macro_rules! impl_query_archetype {
133133
Ok(())
134134
};
135135

136+
137+
let upsert_results = |
138+
data_time: TimeInt,
139+
arch_view: &::re_query::ArchetypeView<A>,
140+
bucket: &mut crate::CacheBucket,
141+
| -> crate::Result<()> {
142+
re_log::trace!(data_time=?data_time, ?data_time, "fill");
143+
144+
// Grabbing the current time is quite costly on web.
145+
#[cfg(not(target_arch = "wasm32"))]
146+
let now = web_time::Instant::now();
147+
148+
bucket.[<insert_pov$N _comp$M>]::<A, $($pov,)+ $($comp,)*>(data_time, &arch_view)?;
149+
150+
#[cfg(not(target_arch = "wasm32"))]
151+
{
152+
let elapsed = now.elapsed();
153+
::re_log::trace!(
154+
store_id=%store.id(),
155+
%entity_path,
156+
archetype=%A::name(),
157+
"cached new entry in {elapsed:?} ({:0.3} entries/s)",
158+
1f64 / elapsed.as_secs_f64()
159+
);
160+
}
161+
162+
Ok(())
163+
};
164+
136165
let mut latest_at_callback = |query: &LatestAtQuery, latest_at_cache: &mut crate::LatestAtCache| {
137166
re_tracing::profile_scope!("latest_at", format!("{query:?}"));
138167

139-
let crate::LatestAtCache { per_query_time, per_data_time } = latest_at_cache;
168+
let crate::LatestAtCache { per_query_time, per_data_time, timeless } = latest_at_cache;
140169

141170
let query_time_bucket_at_query_time = match per_query_time.entry(query.at) {
142171
std::collections::btree_map::Entry::Occupied(query_time_bucket_at_query_time) => {
143172
// Fastest path: we have an entry for this exact query time, no need to look any
144173
// further.
145-
return iter_results(&query_time_bucket_at_query_time.get().read());
174+
re_log::trace!(query_time=?query.at, "cache hit (query time)");
175+
return iter_results(false, &query_time_bucket_at_query_time.get().read());
146176
}
147177
entry @ std::collections::btree_map::Entry::Vacant(_) => entry,
148178
};
149179

150180
let arch_view = query_archetype::<A>(store, &query, entity_path)?;
151-
// TODO(cmc): actual timeless caching support.
152-
let data_time = arch_view.data_time().unwrap_or(TimeInt::MIN);
181+
let data_time = arch_view.data_time();
153182

154183
// Fast path: we've run the query and realized that we already have the data for the resulting
155184
// _data_ time, so let's use that to avoid join & deserialization costs.
156-
if let Some(data_time_bucket_at_data_time) = per_data_time.get(&data_time) {
157-
*query_time_bucket_at_query_time.or_default() = std::sync::Arc::clone(&data_time_bucket_at_data_time);
185+
if let Some(data_time) = data_time { // Reminder: `None` means timeless.
186+
if let Some(data_time_bucket_at_data_time) = per_data_time.get(&data_time) {
187+
re_log::trace!(query_time=?query.at, ?data_time, "cache hit (data time)");
188+
189+
*query_time_bucket_at_query_time.or_default() = std::sync::Arc::clone(&data_time_bucket_at_data_time);
158190

159-
// We now know for a fact that a query at that data time would yield the same
160-
// results: copy the bucket accordingly so that the next cache hit for that query
161-
// time ends up taking the fastest path.
162-
let query_time_bucket_at_data_time = per_query_time.entry(data_time);
163-
*query_time_bucket_at_data_time.or_default() = std::sync::Arc::clone(&data_time_bucket_at_data_time);
191+
// We now know for a fact that a query at that data time would yield the same
192+
// results: copy the bucket accordingly so that the next cache hit for that query
193+
// time ends up taking the fastest path.
194+
let query_time_bucket_at_data_time = per_query_time.entry(data_time);
195+
*query_time_bucket_at_data_time.or_default() = std::sync::Arc::clone(&data_time_bucket_at_data_time);
164196

165-
return iter_results(&data_time_bucket_at_data_time.read());
197+
return iter_results(false, &data_time_bucket_at_data_time.read());
198+
}
199+
} else {
200+
if let Some(timeless_bucket) = timeless.as_ref() {
201+
re_log::trace!(query_time=?query.at, "cache hit (data time, timeless)");
202+
return iter_results(true, timeless_bucket);
203+
}
166204
}
167205

168206
let query_time_bucket_at_query_time = query_time_bucket_at_query_time.or_default();
169207

170208
// Slowest path: this is a complete cache miss.
171-
{
172-
re_tracing::profile_scope!("fill");
209+
if let Some(data_time) = data_time { // Reminder: `None` means timeless.
210+
re_log::trace!(query_time=?query.at, ?data_time, "cache miss");
173211

174-
// Grabbing the current time is quite costly on web.
175-
#[cfg(not(target_arch = "wasm32"))]
176-
let now = web_time::Instant::now();
177-
178-
let mut query_time_bucket_at_query_time = query_time_bucket_at_query_time.write();
179-
query_time_bucket_at_query_time.[<insert_pov$N _comp$M>]::<A, $($pov,)+ $($comp,)*>(query.at, &arch_view)?;
180-
181-
#[cfg(not(target_arch = "wasm32"))]
182212
{
183-
let elapsed = now.elapsed();
184-
::re_log::trace!(
185-
store_id=%store.id(),
186-
%entity_path,
187-
archetype=%A::name(),
188-
"cached new entry in {elapsed:?} ({:0.3} entries/s)",
189-
1f64 / elapsed.as_secs_f64()
190-
);
213+
let mut query_time_bucket_at_query_time = query_time_bucket_at_query_time.write();
214+
upsert_results(data_time, &arch_view, &mut query_time_bucket_at_query_time)?;
191215
}
192-
}
193216

194-
let data_time_bucket_at_data_time = per_data_time.entry(data_time);
195-
*data_time_bucket_at_data_time.or_default() = std::sync::Arc::clone(&query_time_bucket_at_query_time);
217+
let data_time_bucket_at_data_time = per_data_time.entry(data_time);
218+
*data_time_bucket_at_data_time.or_default() = std::sync::Arc::clone(&query_time_bucket_at_query_time);
219+
220+
iter_results(false, &query_time_bucket_at_query_time.read())
221+
} else {
222+
re_log::trace!(query_time=?query.at, "cache miss (timeless)");
223+
224+
let mut timeless_bucket = crate::CacheBucket::default();
196225

197-
iter_results(&query_time_bucket_at_query_time.read())
226+
upsert_results(TimeInt::MIN, &arch_view, &mut timeless_bucket)?;
227+
iter_results(true, &timeless_bucket)?;
228+
229+
*timeless = Some(timeless_bucket);
230+
Ok(())
231+
}
198232
};
199233

200234

@@ -209,8 +243,7 @@ macro_rules! impl_query_archetype {
209243

210244
for arch_view in arch_views {
211245
let data = (
212-
// TODO(cmc): actual timeless caching support.
213-
(arch_view.data_time().unwrap_or(TimeInt::MIN), arch_view.primary_row_id()),
246+
(arch_view.data_time(), arch_view.primary_row_id()),
214247
MaybeCachedComponentData::Raw(arch_view.iter_instance_keys().collect()),
215248
$(MaybeCachedComponentData::Raw(arch_view.iter_required_component::<$pov>()?.collect()),)+
216249
$(MaybeCachedComponentData::Raw(arch_view.iter_optional_component::<$comp>()?.collect()),)*
@@ -228,8 +261,7 @@ macro_rules! impl_query_archetype {
228261
let arch_view = ::re_query::query_archetype::<A>(store, query, entity_path)?;
229262

230263
let data = (
231-
// TODO(cmc): actual timeless caching support.
232-
(arch_view.data_time().unwrap_or(TimeInt::MIN), arch_view.primary_row_id()),
264+
(arch_view.data_time(), arch_view.primary_row_id()),
233265
MaybeCachedComponentData::Raw(arch_view.iter_instance_keys().collect()),
234266
$(MaybeCachedComponentData::Raw(arch_view.iter_required_component::<$pov>()?.collect()),)+
235267
$(MaybeCachedComponentData::Raw(arch_view.iter_optional_component::<$comp>()?.collect()),)*
@@ -286,7 +318,7 @@ where
286318
R1: Component + Send + Sync + 'static,
287319
F: FnMut(
288320
(
289-
(TimeInt, RowId),
321+
(Option<TimeInt>, RowId),
290322
MaybeCachedComponentData<'_, InstanceKey>,
291323
MaybeCachedComponentData<'_, R1>,
292324
),
@@ -318,7 +350,7 @@ macro_rules! impl_query_archetype_with_history {
318350
$($comp: Component + Send + Sync + 'static,)*
319351
F: FnMut(
320352
(
321-
(TimeInt, RowId),
353+
(Option<TimeInt>, RowId),
322354
MaybeCachedComponentData<'_, InstanceKey>,
323355
$(MaybeCachedComponentData<'_, $pov>,)+
324356
$(MaybeCachedComponentData<'_, Option<$comp>>,)*

crates/re_space_view_spatial/src/visualizers/entity_iterator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ macro_rules! impl_process_archetype {
137137
&EntityPath,
138138
&EntityProperties,
139139
&SpatialSceneEntityContext<'_>,
140-
(TimeInt, RowId),
140+
(Option<TimeInt>, RowId),
141141
&[InstanceKey],
142142
$(&[$pov],)*
143143
$(&[Option<$comp>],)*

crates/re_space_view_text_log/src/visualizer_system.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use re_data_store::TimeRange;
22
use re_entity_db::EntityPath;
3-
use re_log_types::{RowId, TimeInt};
3+
use re_log_types::RowId;
44
use re_types::{
55
archetypes::TextLog,
66
components::{Color, Text, TextLogLevel},
@@ -61,6 +61,8 @@ impl VisualizerSystem for TextLogSystem {
6161
let store = ctx.entity_db.store();
6262

6363
for data_result in query.iter_visible_data_results(Self::identifier()) {
64+
re_tracing::profile_scope!("primary", &data_result.entity_path.to_string());
65+
6466
// We want everything, for all times:
6567
let timeline_query =
6668
re_data_store::RangeQuery::new(query.timeline, TimeRange::EVERYTHING);
@@ -77,8 +79,7 @@ impl VisualizerSystem for TextLogSystem {
7779
self.entries.push(Entry {
7880
row_id,
7981
entity_path: data_result.entity_path.clone(),
80-
// TODO(cmc): real support for timeless data in caches.
81-
time: (time != TimeInt::MIN).then(|| time.as_i64()),
82+
time: time.map(|time| time.as_i64()),
8283
color: *color,
8384
body: body.clone(),
8485
level: level.clone(),
@@ -88,11 +89,6 @@ impl VisualizerSystem for TextLogSystem {
8889
)?;
8990
}
9091

91-
{
92-
re_tracing::profile_scope!("sort");
93-
self.entries.sort_by_key(|entry| entry.time);
94-
}
95-
9692
Ok(Vec::new())
9793
}
9894

0 commit comments

Comments
 (0)