Skip to content

Commit 0ba7dc5

Browse files
authored
Small performance improvement for many entities (#11720)
1 parent eea1d8d commit 0ba7dc5

File tree

6 files changed

+463
-18
lines changed

6 files changed

+463
-18
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10386,11 +10386,14 @@ version = "0.27.0-alpha.8+dev"
1038610386
dependencies = [
1038710387
"ahash",
1038810388
"arrow",
10389+
"criterion",
1038910390
"egui",
1039010391
"egui_tiles",
1039110392
"itertools 0.14.0",
10393+
"mimalloc",
1039210394
"nohash-hasher",
1039310395
"parking_lot",
10396+
"rand 0.9.2",
1039410397
"re_chunk",
1039510398
"re_chunk_store",
1039610399
"re_entity_db",

crates/store/re_log_types/src/path/entity_path_filter.rs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,82 @@ impl ResolvedEntityPathFilter {
782782
pub fn rules(&self) -> impl Iterator<Item = (&ResolvedEntityPathRule, &RuleEffect)> {
783783
self.rules.iter()
784784
}
785+
786+
/// Evaluate how a path matches against this filter.
787+
///
788+
/// This returns detailed information about:
789+
/// - Whether any part of the subtree rooted at this path should be included
790+
/// - Whether this specific path matches the filter
791+
/// - Whether this specific path is explicitly included (not just via subtree)
792+
pub fn evaluate(&self, path: &EntityPath) -> FilterEvaluation {
793+
let mut subtree_included = false;
794+
let mut matches_exactly = false;
795+
let mut last_match: Option<(RuleEffect, bool)> = None;
796+
let mut found_include_in_subtree = false;
797+
798+
for (rule, effect) in self.rules() {
799+
if !found_include_in_subtree
800+
&& *effect == RuleEffect::Include
801+
&& rule.resolved_path.starts_with(path)
802+
{
803+
found_include_in_subtree = true;
804+
subtree_included = true;
805+
}
806+
807+
if !matches_exactly
808+
&& *effect == RuleEffect::Include
809+
&& !rule.rule.include_subtree()
810+
&& rule.resolved_path == *path
811+
{
812+
matches_exactly = true;
813+
}
814+
815+
if rule.matches(path) {
816+
last_match = Some((*effect, rule.rule.include_subtree()));
817+
}
818+
}
819+
820+
if let Some((effect, include_subtree)) = last_match {
821+
match effect {
822+
RuleEffect::Include => subtree_included = true,
823+
RuleEffect::Exclude => {
824+
if include_subtree && !found_include_in_subtree {
825+
// Entire subtree is excluded, and we've already checked that nothing
826+
// in the subtree was explicitly included.
827+
subtree_included = false;
828+
}
829+
}
830+
}
831+
}
832+
833+
let matches = last_match.is_some_and(|(effect, _)| effect == RuleEffect::Include);
834+
835+
FilterEvaluation {
836+
subtree_included,
837+
matches,
838+
matches_exactly,
839+
}
840+
}
841+
}
842+
843+
/// Result of evaluating a filter against an entity path.
844+
///
845+
/// This provides detailed information about how a path matches a filter,
846+
/// which is useful for efficiently walking entity trees.
847+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
848+
pub struct FilterEvaluation {
849+
/// Whether any part of the subtree rooted at this path should be included.
850+
///
851+
/// If `false`, the entire subtree can be skipped during tree traversal.
852+
pub subtree_included: bool,
853+
854+
/// Whether this specific path matches the filter.
855+
pub matches: bool,
856+
857+
/// Whether this specific path is explicitly included (not just via a subtree rule).
858+
///
859+
/// This is `true` when there's an exact (non-subtree) inclusion rule for this path.
860+
pub matches_exactly: bool,
785861
}
786862

787863
impl EntityPathRule {
@@ -1287,6 +1363,154 @@ mod tests {
12871363
);
12881364
}
12891365

1366+
#[test]
1367+
fn test_evaluate_filter() {
1368+
let subst_env = EntityPathSubs::empty();
1369+
1370+
// Test with a simple include-all filter
1371+
let filter = EntityPathFilter::parse_forgiving("+ /**").resolve_forgiving(&subst_env);
1372+
1373+
let eval = filter.evaluate(&EntityPath::from("/world"));
1374+
assert!(eval.subtree_included, "/**should include /world subtree");
1375+
assert!(eval.matches, "/**should match /world");
1376+
assert!(!eval.matches_exactly, "/** doesn't exactly match /world");
1377+
1378+
// Test with complex filter rules
1379+
let filter = EntityPathFilter::parse_forgiving(
1380+
r"
1381+
+ /world/**
1382+
- /world/car/**
1383+
+ /world/car/driver
1384+
",
1385+
)
1386+
.resolve_forgiving(&subst_env);
1387+
1388+
// Test /world - should be included
1389+
let eval = filter.evaluate(&EntityPath::from("/world"));
1390+
assert!(eval.subtree_included, "/world subtree should be included");
1391+
assert!(eval.matches, "/world should match");
1392+
assert!(
1393+
!eval.matches_exactly,
1394+
"/world should not match exactly (matched by /world/** subtree rule)"
1395+
);
1396+
1397+
// Test /world/house - should be included by /world/**
1398+
let eval = filter.evaluate(&EntityPath::from("/world/house"));
1399+
assert!(
1400+
eval.subtree_included,
1401+
"/world/house subtree should be included"
1402+
);
1403+
assert!(eval.matches, "/world/house should match");
1404+
assert!(
1405+
!eval.matches_exactly,
1406+
"/world/house should not match exactly (matched by /world/** subtree rule)"
1407+
);
1408+
1409+
// Test /world/car - should not match but subtree should be included (has exception deeper)
1410+
let eval = filter.evaluate(&EntityPath::from("/world/car"));
1411+
assert!(
1412+
eval.subtree_included,
1413+
"/world/car subtree should be included (has /world/car/driver exception)"
1414+
);
1415+
assert!(!eval.matches, "/world/car should not match");
1416+
assert!(
1417+
!eval.matches_exactly,
1418+
"/world/car should not match exactly (excluded by - /world/car/**)"
1419+
);
1420+
1421+
// Test /world/car/driver - should be included
1422+
let eval = filter.evaluate(&EntityPath::from("/world/car/driver"));
1423+
assert!(
1424+
eval.subtree_included,
1425+
"/world/car/driver subtree should be included"
1426+
);
1427+
assert!(eval.matches, "/world/car/driver should match");
1428+
assert!(
1429+
eval.matches_exactly,
1430+
"/world/car/driver should match exactly (non-subtree rule)"
1431+
);
1432+
1433+
// Test /world/car/hood - should be excluded
1434+
let eval = filter.evaluate(&EntityPath::from("/world/car/hood"));
1435+
assert!(
1436+
!eval.subtree_included,
1437+
"/world/car/hood subtree should be excluded"
1438+
);
1439+
assert!(!eval.matches, "/world/car/hood should not match");
1440+
assert!(
1441+
!eval.matches_exactly,
1442+
"/world/car/hood should not match exactly (excluded)"
1443+
);
1444+
1445+
// Test /other - should be excluded (no matching rule)
1446+
let eval = filter.evaluate(&EntityPath::from("/other"));
1447+
assert!(!eval.subtree_included, "/other subtree should be excluded");
1448+
assert!(!eval.matches, "/other should not match");
1449+
assert!(
1450+
!eval.matches_exactly,
1451+
"/other should not match exactly (no matching rule)"
1452+
);
1453+
1454+
// Test exact match without subtree
1455+
let filter = EntityPathFilter::parse_forgiving(
1456+
r"
1457+
+ /world
1458+
+ /world/car/driver
1459+
",
1460+
)
1461+
.resolve_forgiving(&subst_env);
1462+
1463+
let eval = filter.evaluate(&EntityPath::from("/world"));
1464+
assert!(eval.subtree_included, "/world should be included");
1465+
assert!(eval.matches, "/world should match");
1466+
assert!(
1467+
eval.matches_exactly,
1468+
"/world should match exactly (non-subtree rule)"
1469+
);
1470+
1471+
// Children should not be included (no subtree rule)
1472+
let eval = filter.evaluate(&EntityPath::from("/world/house"));
1473+
assert!(
1474+
!eval.subtree_included,
1475+
"/world/house should not be included (parent has no subtree rule)"
1476+
);
1477+
assert!(!eval.matches, "/world/house should not match");
1478+
assert!(
1479+
!eval.matches_exactly,
1480+
"/world/house should not match exactly (no rule for this path)"
1481+
);
1482+
1483+
// Test subtree_included when there's an include rule deeper in the tree
1484+
let filter = EntityPathFilter::parse_forgiving(
1485+
r"
1486+
+ /world/car/driver
1487+
",
1488+
)
1489+
.resolve_forgiving(&subst_env);
1490+
1491+
let eval = filter.evaluate(&EntityPath::from("/world"));
1492+
assert!(
1493+
eval.subtree_included,
1494+
"/world should have subtree_included=true because /world/car/driver is included"
1495+
);
1496+
assert!(!eval.matches, "/world should not match directly");
1497+
assert!(
1498+
!eval.matches_exactly,
1499+
"/world should not match exactly (no rule for this path)"
1500+
);
1501+
1502+
let eval = filter.evaluate(&EntityPath::from("/world/car"));
1503+
assert!(
1504+
eval.subtree_included,
1505+
"/world/car should have subtree_included=true because /world/car/driver is included"
1506+
);
1507+
assert!(!eval.matches, "/world/car should not match directly");
1508+
assert!(
1509+
!eval.matches_exactly,
1510+
"/world/car should not match exactly (no rule for this path)"
1511+
);
1512+
}
1513+
12901514
#[test]
12911515
fn test_unresolved() {
12921516
// We should omit the properties from the unresolved filter.

crates/store/re_log_types/src/path/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use component_path::ComponentPath;
1515
pub use data_path::DataPath;
1616
pub use entity_path::{EntityPath, EntityPathHash};
1717
pub use entity_path_filter::{
18-
EntityPathFilter, EntityPathFilterError, EntityPathRule, EntityPathSubs,
18+
EntityPathFilter, EntityPathFilterError, EntityPathRule, EntityPathSubs, FilterEvaluation,
1919
ResolvedEntityPathFilter, ResolvedEntityPathRule, RuleEffect,
2020
};
2121
pub use entity_path_part::EntityPathPart;

crates/viewer/re_viewport_blueprint/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,13 @@ thiserror.workspace = true
4343

4444
[dev-dependencies]
4545
re_test_context.workspace = true
46+
criterion.workspace = true
47+
mimalloc.workspace = true
48+
rand = { workspace = true, features = ["std", "std_rng"] }
49+
50+
[lib]
51+
bench = false
52+
53+
[[bench]]
54+
name = "data_query"
55+
harness = false

0 commit comments

Comments
 (0)