Skip to content

Commit 0be8bec

Browse files
committed
Execute spatial predicates with RowFn
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 348583f commit 0be8bec

8 files changed

Lines changed: 862 additions & 458 deletions

File tree

‎Cargo.toml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,13 @@ flatbuffers = "25.2.10"
162162
fsst-rs = "0.6.0"
163163
futures = { version = "0.3.31", default-features = false }
164164
fuzzy-matcher = "0.3"
165-
geo = "0.31.0"
165+
# `vortex-spatial`'s `contains_route` transcribes geo's `impl_contains_from_relate!` dispatch
166+
# table, so any bump that moves a row silently changes containment verdicts. The tests stay green
167+
# wherever relate and the direct algorithm agree. Pinned exactly so that taking any new geo,
168+
# patch releases included, is a deliberate edit of this line that re-verifies the table; a caret
169+
# requirement would let `cargo update` (or automated lockfile maintenance) take 0.31.x with no diff
170+
# to review. See `vortex-spatial/src/scalar_fn/contains.rs`.
171+
geo = "=0.31.0"
166172
geo-traits = "0.3.0"
167173
geo-types = "0.7.19"
168174
geoarrow = "0.8.0"

‎vortex-spatial/benches/binary_predicates.rs‎

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@
44
//! Microbenchmarks for the binary geometry predicates `ST_Contains` and `ST_Intersects`, focused
55
//! on the cost of a batch-constant operand.
66
//!
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
88
//! column. Arms pair it with a point column (the external `geo` crate answers those pairings with
99
//! direct point-in-polygon algorithms) and with a small-polygon column (`geo` routes those through
1010
//! bounding-box prechecks and `relate`), covering both a mostly-disjoint dataset where the bbox
1111
//! early-out rejects nearly every row and an all-overlapping one where it never does. The
1212
//! column-x-column arms are the control: no operand is constant, so a prepared path has nothing to
1313
//! hoist and must not regress them.
1414
//!
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-
//!
2015
//! Run with `cargo bench -p vortex-spatial --bench binary_predicates`.
2116
2217
#![expect(clippy::unwrap_used)]
@@ -54,25 +49,27 @@ fn main() {
5449

5550
static SESSION: LazyLock<VortexSession> = LazyLock::new(spatial_session);
5651

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;
6255

6356
/// The all-overlapping polygon arm never rejects on bounding boxes, so every row pays for the full
6457
/// 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;
6663

6764
/// Deterministic pseudo-random value in `[0, 1)`.
6865
fn unit(i: usize) -> f64 {
6966
((i.wrapping_mul(2654435761) >> 8) % 10_000) as f64 / 10_000.0
7067
}
7168

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.
7471
fn query_ring(cx: f64, cy: f64) -> Vec<(f64, f64)> {
75-
let n = 128;
72+
let n = 32;
7673
(0..=n)
7774
.map(|i| {
7875
let theta = (i % n) as f64 / n as f64 * TAU;
@@ -222,6 +219,23 @@ mod contains {
222219
});
223220
}
224221

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+
225239
/// Constant container against a point column with one null row in eight.
226240
#[divan::bench]
227241
fn constant_x_nullable_points(bencher: Bencher) {

0 commit comments

Comments
 (0)