Parallelize grid pyramid construction with rayon

Use par_iter for Level 0 cells (742K cells with Gaussian scoring)
and for each coarse level within-level parallelization.

Performance: 1230µs → 670µs (1.84x speedup, 45% improvement)
on 10-core Apple Silicon.
This commit is contained in:
codetiger
2026-01-01 12:12:48 +05:30
parent 939f9bc3ae
commit 7240d83494
4 changed files with 141 additions and 35 deletions
+26 -1
View File
@@ -3,6 +3,7 @@
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use std::f32::consts::PI;
use vastu_slam::{LidarScan, MapConfig, OccupancyGridMap, Pose2D};
use vastu_slam::matching::PrecomputedGrids;
/// Create a room scan for benchmarking.
fn room_scan(
@@ -140,10 +141,34 @@ fn bench_coverage_stats(c: &mut Criterion) {
});
}
fn bench_grid_pyramid(c: &mut Criterion) {
let config = MapConfig::default();
let mut map = OccupancyGridMap::new(config);
// Build up a map with some structure
for i in 0..20 {
let angle = i as f32 * 0.2;
let pose = Pose2D::new(3.0, 3.0, angle);
let scan = room_scan(6.0, 6.0, 3.0, 3.0, 360);
map.observe_lidar(&scan, pose);
}
let storage = map.storage();
let (w, h) = (storage.width(), storage.height());
c.bench_function(&format!("grid_pyramid_{}x{}", w, h), |b| {
b.iter(|| {
let grids = PrecomputedGrids::from_storage(black_box(storage));
black_box(grids)
})
});
}
criterion_group!(
benches,
bench_map_update,
bench_map_update_resolutions,
bench_coverage_stats
bench_coverage_stats,
bench_grid_pyramid
);
criterion_main!(benches);
+36
View File
@@ -52,6 +52,38 @@ This file tracks performance, accuracy, and quality metrics across commits for o
## Benchmark History
### Entry: 2026-01-01 (Grid Pyramid Parallelization)
**Commit:** `db285a3`
**Message:** Parallelize grid pyramid construction with rayon
**Platform:** macOS Darwin 25.2.0 (Apple Silicon, 10 cores)
**Rust:** nightly (portable_simd feature)
#### Grid Pyramid Build Performance
| Metric | Sequential | Parallel | Speedup |
|--------|------------|----------|---------|
| grid_pyramid_863x860 | 1230 µs | 670.43 µs | **1.84x** |
#### Implementation Details
| Component | Cells | Parallelization |
|-----------|-------|-----------------|
| Level 0 | 742K | `par_iter` over flat index |
| Level 1 | 185K | `par_iter` within level |
| Level 2 | 46K | `par_iter` within level |
| Level 3 | 11K | `par_iter` within level |
#### Notes
- Parallelized Level 0 construction (742K cells with Gaussian exp())
- Parallelized coarse levels within each level (max of 2x2 regions)
- Levels must be built sequentially (level i depends on level i-1)
- Also parallelized `update_from_storage()` for incremental updates
- 1.84x speedup (45% improvement) on 10-core Apple Silicon
---
### Entry: 2026-01-01 (Rayon Parallelization)
**Commit:** `a1e3d28e6d5c77f0107a89ee95251ba8b59c1ad1`
@@ -244,6 +276,10 @@ scan_matching_360 (µs)
├─ 2026-01-01: 1716.1 µs (sequential, inlined)
├─ 2025-12-31: 1793.7 µs (baseline)
│
grid_pyramid_863x860 (µs)
├─ 2026-01-01: 670.43 µs (parallel, 1.84x speedup)
├─ 2026-01-01: 1230 µs (sequential)
│
grid_update_360 (µs)
├─ 2025-12-31: 209.88 µs (baseline)
│
+28
View File
@@ -1,6 +1,34 @@
{
"version": "1.0",
"entries": [
{
"commit": "db285a3",
"date": "2026-01-01T19:30:00+05:30",
"message": "Parallelize grid pyramid construction with rayon",
"platform": {
"os": "Darwin 25.2.0",
"arch": "arm64",
"rust": "nightly",
"cpu_cores": 10
},
"performance": {
"grid_pyramid": {
"863x860_sequential_us": 1230,
"863x860_parallel_us": 670.43,
"speedup": 1.84
}
},
"parallel_config": {
"rayon_version": "1.10",
"parallelization_target": "Level 0 cells + coarse level cells"
},
"notes": [
"Parallelized Level 0 construction (742K cells with Gaussian exp())",
"Parallelized coarse levels within each level (max of 2x2 regions)",
"Achieved 1.84x speedup (45% improvement) on 10-core Apple Silicon",
"Also parallelized update_from_storage() for incremental updates"
]
},
{
"commit": "a1e3d28e6d5c77f0107a89ee95251ba8b59c1ad1",
"date": "2026-01-01T18:00:00+05:30",
+51 -34
View File
@@ -3,6 +3,7 @@
use crate::core::simd::{PointCloud, RotationMatrix4, transform_points_simd4};
use crate::core::{GridCoord, Pose2D, WorldPoint};
use crate::grid::GridStorage;
use rayon::prelude::*;
use super::config::NUM_LEVELS;
@@ -49,24 +50,28 @@ impl PrecomputedGrids {
let sigma = resolution * 2.0;
let two_sigma_sq = 2.0 * sigma * sigma;
for y in 0..height {
for x in 0..width {
// Parallel Level 0 construction: each cell is independent
levels[0] = (0..height * width)
.into_par_iter()
.map(|idx| {
let y = idx / width;
let x = idx % width;
let coord = GridCoord::new(x as i32, y as i32);
let distance = storage.get_distance(coord);
// Gaussian score: high near walls, low far from walls
let score = if distance < f32::MAX / 2.0 {
if distance < f32::MAX / 2.0 {
(-distance * distance / two_sigma_sq).exp()
} else {
// Unknown area - small positive score for exploration
0.1
};
levels[0].push(score);
}
}
}
})
.collect();
// Build coarser levels by taking max of 2x2 regions
// Levels must be built sequentially (level i depends on level i-1)
// but within each level, cells are independent and can be parallelized
for level in 1..NUM_LEVELS {
let prev_width = widths[level - 1];
let prev_height = heights[level - 1];
@@ -78,10 +83,16 @@ impl PrecomputedGrids {
widths[level] = new_width;
heights[level] = new_height;
let mut new_grid = vec![0.0f32; new_width * new_height];
// Reference to previous level for parallel access
let prev_level = &levels[level - 1];
// Parallel construction of this level
let new_grid: Vec<f32> = (0..new_height * new_width)
.into_par_iter()
.map(|idx| {
let y = idx / new_width;
let x = idx % new_width;
for y in 0..new_height {
for x in 0..new_width {
// Find max of 2x2 region in previous level
let mut max_val = 0.0f32;
@@ -91,15 +102,15 @@ impl PrecomputedGrids {
let py = y * 2 + dy;
if px < prev_width && py < prev_height {
let idx = py * prev_width + px;
max_val = max_val.max(levels[level - 1][idx]);
let pidx = py * prev_width + px;
max_val = max_val.max(prev_level[pidx]);
}
}
}
new_grid[y * new_width + x] = max_val;
}
}
max_val
})
.collect();
levels[level] = new_grid;
}
@@ -288,35 +299,41 @@ impl PrecomputedGrids {
return;
}
// Update level 0
// Update level 0 (parallel)
let sigma = self.resolution * 2.0;
let two_sigma_sq = 2.0 * sigma * sigma;
self.levels[0].clear();
for y in 0..height {
for x in 0..width {
self.levels[0] = (0..height * width)
.into_par_iter()
.map(|idx| {
let y = idx / width;
let x = idx % width;
let coord = GridCoord::new(x as i32, y as i32);
let distance = storage.get_distance(coord);
let score = if distance < f32::MAX / 2.0 {
if distance < f32::MAX / 2.0 {
(-distance * distance / two_sigma_sq).exp()
} else {
0.1
};
}
})
.collect();
self.levels[0].push(score);
}
}
// Rebuild coarser levels
// Rebuild coarser levels (parallel within each level)
for level in 1..NUM_LEVELS {
let prev_width = self.widths[level - 1];
let prev_height = self.heights[level - 1];
let new_width = self.widths[level];
let new_height = self.heights[level];
for y in 0..new_height {
for x in 0..new_width {
let prev_level = &self.levels[level - 1];
self.levels[level] = (0..new_height * new_width)
.into_par_iter()
.map(|idx| {
let y = idx / new_width;
let x = idx % new_width;
let mut max_val = 0.0f32;
for dy in 0..2 {
@@ -325,15 +342,15 @@ impl PrecomputedGrids {
let py = y * 2 + dy;
if px < prev_width && py < prev_height {
let idx = py * prev_width + px;
max_val = max_val.max(self.levels[level - 1][idx]);
let pidx = py * prev_width + px;
max_val = max_val.max(prev_level[pidx]);
}
}
}
self.levels[level][y * new_width + x] = max_val;
}
}
max_val
})
.collect();
}
}