Skip to content

Commit e1f47e1

Browse files
committed
perf(osm): parallel ring assembly + linestring resolution
`relation_to_admin` does two independent expensive passes per admin relation: 1. `assemble_rings(outer)` and `assemble_rings(inner)` — endpoint- matching ring stitcher. Each ring set is independent; outer + inner have no shared state. 2. `ring_to_linestring(ring, node_coords)` for each ring — node-id to coord lookup. Each ring resolves independently against the shared (read-only) `node_coords` cache. Both passes ran sequentially. Big admin relations (coastlines stitched from thousands of ways, country borders with deep hierarchies) bottlenecked on the ring assembly pass — observed ~1-2 s wall-clock for some relations on a single thread, blocking the parent blob's `par_bridge` worker. Two surgical edits inside `relation_to_admin`: - `rayon::join` runs outer + inner ring stitching in parallel. Free for cheap relations (one side returns immediately) and cuts wall-clock by ~50% on relations with substantial rings on both sides. - `into_par_iter()` over each ring set's `ring_to_linestring` resolution. Per-ring work is independent; rayon's work-stealing pool spreads the linear-scan node lookups across cores. Both calls live inside an already-parallel context (`parallel_admin_relations` -> `par_bridge`), so we're nesting parallelism inside rayon. rayon handles this correctly via its work-stealing scheduler — there's no thread blow-up. Combined with the per-tile-write parallelism (620fc35), the OSM critical path now scales close to linearly with cores on the two passes that previously dominated wall-clock (relations ~120 s of a 633 s DE bench, tile encode ~30 s). Workspace tests stay green; clippy clean.
1 parent 620fc35 commit e1f47e1

1 file changed

Lines changed: 17 additions & 4 deletions

File tree

  • crates/cairn-import-osm/src

crates/cairn-import-osm/src/lib.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,24 +1541,37 @@ fn relation_to_admin(
15411541
return None;
15421542
}
15431543

1544-
let outer_rings = assemble_rings(&outer_ways, way_nodes);
1544+
// Outer + inner ring assembly are independent; for big admin
1545+
// relations (e.g. coastlines stitched from thousands of ways)
1546+
// running them in parallel via rayon::join cuts wall-clock by
1547+
// ~50 % on the relation. Cheap relations don't notice the
1548+
// overhead — rayon::join is essentially free when one side
1549+
// returns immediately.
1550+
let (outer_rings, inner_rings) = rayon::join(
1551+
|| assemble_rings(&outer_ways, way_nodes),
1552+
|| assemble_rings(&inner_ways, way_nodes),
1553+
);
15451554
if outer_rings.is_empty() {
15461555
counters.skipped_relation_open_ring += 1;
15471556
debug!(rel_id = relation.id(), "no closed outer ring; dropping");
15481557
return None;
15491558
}
1550-
let inner_rings = assemble_rings(&inner_ways, way_nodes);
15511559

1560+
// ring_to_linestring is independent per ring (only reads
1561+
// node_coords). par_iter() across rings is a free win for
1562+
// multi-ring relations; for the typical single-ring case
1563+
// par_iter falls back to sequential with negligible overhead.
1564+
use rayon::prelude::*;
15521565
let outer_linestrings: Vec<LineString<f64>> = outer_rings
1553-
.into_iter()
1566+
.into_par_iter()
15541567
.filter_map(|ring| ring_to_linestring(&ring, node_coords))
15551568
.collect();
15561569
if outer_linestrings.is_empty() {
15571570
counters.skipped_relation_open_ring += 1;
15581571
return None;
15591572
}
15601573
let inner_linestrings: Vec<LineString<f64>> = inner_rings
1561-
.into_iter()
1574+
.into_par_iter()
15621575
.filter_map(|ring| ring_to_linestring(&ring, node_coords))
15631576
.collect();
15641577
let polygons = assemble_polygons(outer_linestrings, inner_linestrings, counters);

0 commit comments

Comments
 (0)