Skip to main content

GPU spatial query benchmark

Run on your device

Live luSpatial: CPU vs. WebGPU

Run the same bounds predicate and compact matching point IDs on your CPU, an unindexed WebGPU graph, and a reusable WebGPU grid index. GPU timings include command encoding, submission, and completed execution.

This opt-in benchmark runs a deterministic CPU bounds scan, the actual GPUPointSpatialQuery scan, and the same query against a reusable GPUGridIndex. Every GPU result must match the complete CPU-selected ID set. Grid construction is reported separately because its cost must be amortized across repeated queries; the timed GPU paths include graph encoding, submission, and a completion fence, not merely CPU-side command submission.

Overview

runGPUSpatialQueryBenchmark compares unindexed scan, uniform-grid, and BVH query paths with one correctness gate and one measurement protocol. Each adapter encodes its existing command graph, reads exact stable IDs, classifies graph nodes as build, refit, query, or refinement work, and reports memory plus observable candidate or traversal counts.

The harness exists because “use an index” is not a useful performance rule. An index pays an up-front construction or update cost and consumes memory. It wins only when pruning and reuse repay that cost on the target adapter and representative data. The harness refuses overflow or a mismatch with the shared CPU oracle before it reports timings, so an incomplete result cannot appear faster.

Concepts

One dataset, query, and oracle

All three paths must consume the same logical records and query. The application computes expected stable IDs on the CPU, then each GPU adapter returns its exact IDs through readResult. Result order is ignored because parallel compaction is unordered; duplicates, omissions, extra IDs, and overflow still fail the run.

Grid candidates must be followed by the same exact predicate used by the scan. BVH leaf bounds are exact only for axis-aligned-bounds queries; another shape needs the same final predicate too. This keeps broad-phase quality separate from answer correctness.

Warmup and repeated measurements

Warmup encodings populate pipelines and caches before measured iterations. The harness then creates a timestamp query set for every measured encoding when the adapter supports timestamp-query, submits the work, and explicitly reads per-node durations. Reports retain minimum, median, 95th percentile, and maximum values instead of selecting one favorable sample. CPU graph-encoding time is always available; GPU time is omitted rather than estimated when timestamps are unsupported.

Use multiple distributions and selectivities. Uniform, clustered, sparse, adversarially ordered, and moving data stress different structures. Tiny, selective, medium, and broad queries expose the difference between effective pruning and bookkeeping overhead.

Phases and amortization

Each adapter maps its stable graph node IDs to these phases:

PhaseWhat it measures
buildGrid clear/count/scan/scatter or another topology construction
refitBVH leaf reload and bottom-up bound propagation after data changes
queryCell enumeration, hierarchy traversal, or unindexed predicate scan
refinementExact application predicate and result compaction after a broad phase

For reuse counts supplied by the caller, the report computes (median build + median refit) / reuse + median query + median refinement. This is a comparison aid, not a universal crossover: update frequency, asynchronous scheduling, contention, and surrounding render work still belong in the consumer benchmark.

Work counters explain timings

Timing alone says which run was faster; work counters help explain why. Grid adapters report the conservative candidateCount. BVH adapters report visitedCount. Every path reports exact result count and owned or borrowed memory bytes. A high candidate-to-result ratio suggests poor cell size or object/grid fit. A high visited-node ratio suggests overlapping or poorly ordered BVH parents.

These counters also make regressions easier to diagnose across adapters: a timing change with stable work often points to runtime or hardware variation, while a work-count change points to the data structure or query.

Selection guidance

SituationStart withWhy
Small input, broad query, or one use before data changesScanNo index construction or storage to amortize
Bounded domain with similarly sized objects and regular occupancyGridParallel build and direct cell lookup are simple and predictable
Sparse domain, widely varying object sizes, or coherent bound groupsBVHHierarchical rejection can avoid empty space and oversized cell fan-out
Unknown or mixed workloadScan baseline plus this harnessThe crossover depends on distribution, selectivity, reuse, and adapter

Do not compare only query kernels. Include allocation or persistent memory, full build, updates, exact refinement, overflow capacity, and the number of queries between changes.

Spatial v1 decision record

The implemented v1 includes full grid rebuild, conservative grid query, exact point refinement, BVH storage/refit, exact point and bounds traversal, and this correctness-gated cost model. Three extensions remain deliberately deferred:

  • Incremental grid maintenance needs a moving-data consumer and a measured crossover that repays reserved-cell memory, fragmentation, and more complex overflow behavior. Full rebuild remains the supported update contract.
  • A Morton or other BVH topology builder needs representative runs showing materially lower visited nodes and end-to-end time than source or producer ordering after its sorting/build cost. Callers can benchmark preordered inputs now without changing BVH storage.
  • Ray or segment traversal needs a picking or simulation consumer to define intersection semantics, nearest-hit behavior, and bounded traversal storage. Bounds and point queries do not pretend to satisfy that separate contract.

These are scope decisions, not claims that the extensions are slow. Each can reopen as a focused feature when a consumer supplies semantics and positive evidence; none is required for the complete spatial filtering v1 contract.

Usage

const report = await runGPUSpatialQueryBenchmark(device, {
paths: [scanPath, gridPath, bvhPath],
expectedIds: cpuOracleIds,
warmupIterations: 5,
measuredIterations: 50,
reuseCounts: [1, 10, 100, 1000]
});

Each path supplies encode(commandEncoder), readResult(), memoryByteLength, and getNodePhase(nodeId). The harness owns command submission and temporary timestamp query sets. The caller owns datasets, graphs, buffers, readback strategy, query generation, and destruction. Keep those adapters next to representative consumers so the benchmark does not hide uploads, repacking, or application predicates.