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:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
│
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user