|
4 | 4 | //! Microbenchmarks for the binary geometry predicates `ST_Contains` and `ST_Intersects`, focused |
5 | 5 | //! on the cost of a batch-constant operand. |
6 | 6 | //! |
7 | | -//! The constant is a 128-vertex query polygon, the shape a spatial filter broadcasts against a |
| 7 | +//! The constant is a 32-vertex query polygon, the shape a spatial filter broadcasts against a |
8 | 8 | //! column. Arms pair it with a point column (the external `geo` crate answers those pairings with |
9 | 9 | //! direct point-in-polygon algorithms) and with a small-polygon column (`geo` routes those through |
10 | 10 | //! bounding-box prechecks and `relate`), covering both a mostly-disjoint dataset where the bbox |
11 | 11 | //! early-out rejects nearly every row and an all-overlapping one where it never does. The |
12 | 12 | //! column-x-column arms are the control: no operand is constant, so a prepared path has nothing to |
13 | 13 | //! hoist and must not regress them. |
14 | 14 | //! |
15 | | -//! `contains` has no all-overlapping arm. One `contains(query polygon, contained square)` row |
16 | | -//! builds a topology graph over the constant's 128 edges, which CodSpeed's CPU simulation charges |
17 | | -//! around 120 µs, so no row count both fits the per-iteration budget and exercises the row loop. |
18 | | -//! [`intersects::polygons_overlapping_x_constant`] covers the never-rejects case instead. |
19 | | -//! |
20 | 15 | //! Run with `cargo bench -p vortex-spatial --bench binary_predicates`. |
21 | 16 |
|
22 | 17 | #![expect(clippy::unwrap_used)] |
@@ -54,25 +49,27 @@ fn main() { |
54 | 49 |
|
55 | 50 | static SESSION: LazyLock<VortexSession> = LazyLock::new(spatial_session); |
56 | 51 |
|
57 | | -/// The ordinary arms use the same row count so results are comparable across shapes. A geometry |
58 | | -/// predicate costs roughly a microsecond per row under CodSpeed's CPU simulation, which caps the |
59 | | -/// row count well below a typical batch to stay inside the 1 ms per-iteration budget from |
60 | | -/// `docs/developer-guide/benchmarking.md`. |
61 | | -const ROWS: usize = 1 << 5; |
| 52 | +/// The ordinary arms use the same row count so results are comparable across shapes. The small |
| 53 | +/// fixture keeps geometry decoding and preparation inside CodSpeed's 1 ms per-iteration budget. |
| 54 | +const ROWS: usize = 1 << 4; |
62 | 55 |
|
63 | 56 | /// The all-overlapping polygon arm never rejects on bounding boxes, so every row pays for the full |
64 | 57 | /// pairwise predicate. It needs a smaller fixture than [`ROWS`] to stay inside the same budget. |
65 | | -const OVERLAPPING_POLYGON_ROWS: usize = 1 << 4; |
| 58 | +const OVERLAPPING_POLYGON_ROWS: usize = 1 << 3; |
| 59 | + |
| 60 | +/// Containment builds a topology graph for each polygon pair. Two rows exercise construction |
| 61 | +/// followed by reuse of the prepared constant geometry while staying inside the benchmark budget. |
| 62 | +const CONTAINED_POLYGON_ROWS: usize = 2; |
66 | 63 |
|
67 | 64 | /// Deterministic pseudo-random value in `[0, 1)`. |
68 | 65 | fn unit(i: usize) -> f64 { |
69 | 66 | ((i.wrapping_mul(2654435761) >> 8) % 10_000) as f64 / 10_000.0 |
70 | 67 | } |
71 | 68 |
|
72 | | -/// The exterior ring of a convex 128-gon of radius 100 centered at `(cx, cy)`: enough vertices |
73 | | -/// that per-row work proportional to the constant's size shows up clearly. |
| 69 | +/// The exterior ring of a convex 32-gon of radius 100 centered at `(cx, cy)`: enough vertices to |
| 70 | +/// exercise work proportional to the constant's size without dominating CodSpeed simulation. |
74 | 71 | fn query_ring(cx: f64, cy: f64) -> Vec<(f64, f64)> { |
75 | | - let n = 128; |
| 72 | + let n = 32; |
76 | 73 | (0..=n) |
77 | 74 | .map(|i| { |
78 | 75 | let theta = (i % n) as f64 / n as f64 * TAU; |
@@ -222,6 +219,23 @@ mod contains { |
222 | 219 | }); |
223 | 220 | } |
224 | 221 |
|
| 222 | + /// Constant container against contained polygons: every bbox check passes, the first row |
| 223 | + /// prepares the constant geometry, and the remaining rows reuse it for the full predicate. |
| 224 | + #[divan::bench] |
| 225 | + fn constant_x_polygons_overlapping(bencher: Bencher) { |
| 226 | + let mut ctx = SESSION.create_execution_ctx(); |
| 227 | + let query = query_constant(&mut ctx, CONTAINED_POLYGON_ROWS); |
| 228 | + let polygons = squares_mostly_overlapping(CONTAINED_POLYGON_ROWS); |
| 229 | + bencher |
| 230 | + .counter(ItemsCount::new(CONTAINED_POLYGON_ROWS)) |
| 231 | + .bench_local(|| { |
| 232 | + execute( |
| 233 | + SpatialContains::try_new(query.clone(), polygons.clone()), |
| 234 | + &mut ctx, |
| 235 | + ) |
| 236 | + }); |
| 237 | + } |
| 238 | + |
225 | 239 | /// Constant container against a point column with one null row in eight. |
226 | 240 | #[divan::bench] |
227 | 241 | fn constant_x_nullable_points(bencher: Bencher) { |
|
0 commit comments