bun-geos
On this page

Benchmarks

Machine: AMD Ryzen 7 260 w/ Radeon 780M Graphics, 16 hw threads · Bun 1.4.3 · GEOS 3.14.1-CAPI-1.20.5 · commit a1fffdc · 2026-10-22T08:02[unknown component: 56].169Z

Best-of-N wall time, warm-up excluded (see bench/run.ts). Regenerate with bun run bench; this page is rendered by bun run bench:report. The competitor comparison below renders when bench/competitors/results.json exists — bun run bench:competitors (provision with bench:competitors:setup). Positioning rule (testing.md §4): robust overlays, repair, and indexed/ batched workloads — never trivial-op marketing.

prepared scan (1 polygon × 100k points)

variant ms
unprepared 181.9
prepared 5.5
prepared+batch 7.6

prepared 33× vs unprepared

STRtree query (100000 items × 1000 envelopes)

variant ms
naive JS envelope scan 546.0
strtree 6.0
strtree queryAll 3.9
strtree+fused filter 25.4

90× vs naive scan

unionAll ladder (961 aligned tiles)

variant ms
coverage 4.1
disjointSubset 10.6
unary 9.8
auto 4.2

coverage 2× vs unary

batch vs singular (10⁵ squares)

variant ms
overlay singular 1411.9
overlay batch 1364.6
mask singular 262.5
mask batch 257.4
measure singular 4.6
measure batch 4.6
wkbRead singular 3647.8
wkbRead batch 514.4

engine-native FFI makes per-object calls cheap; batch = one crossing + C loops (D22 note)

ingest (20000 squares)

variant ms
WKT 336.9
GeoJSON 1004.5
WKB per-geometry 484.2
WKB read-many 24.7

19.6× read-many vs per-geometry

makeValid (1,000 bowties)

variant ms
makeValid 34.6

29 geoms/s

coverageClean (dirty 20×20 coverage)

variant ms
coverageClean 4.6

construction batch (10⁵ squares)

variant ms
hull singular 79.3
hull batch 75.4
centroid batch 39.3

D27 unary batch layer — one crossing per array (D22 note applies)

parallel makeValid (100000 bowties)

variant ms
calling thread 3893.3
worker pool 1053.6

4× vs per-object calls (concurrency 12, chunkFactor 3 — the ≥5× exit axis, D23; WKB serialization bounds the pool at high ratios)

parallel mask scan (100000 bowties × 1 polygon)

variant ms
calling thread 5.0
worker pool 111.6

1× (WKB transfer bounds light ops)

parallel overlay scaling (10⁵ squares × wobble(720))

variant ms
1 worker 416.5
2 workers 354.0
4 workers 404.3
8 workers 423.3

serialization-bound: the main thread builds 100k chunk WKBs per op (~1s) — cheap-geometry overlays don't parallelize past it; heavy per-item ops do

Reading the batch numbers honestly (D22)

On Bun 1.4.x the engine-native FFI makes per-object calls cheap (~tens of ns), so main-thread shim batch paths converge toward the GEOS-bound cost of the workload (~1× on overlay; 0.8–1.2× on masks/measures). The batch layer's measured ≥5× axis is the worker pool (see the parallel makeValid rows): the one-crossing property remains load-bearing for interrupt granularity (between-item aborts), all-or-nothing error contracts, and future Bun versions where per-call costs regress.


Competitor comparison

Same seeded workloads, same methodology (best-of-N wall ms, warm-up excluded, answer fingerprints accumulated inside the timed region), every boundary: native FFI (bun-geos), direct C (geos-cpp), WASM (geos-wasm), ctypes (Shapely), and pure JS (JSTS / turf / polygon-clipping / rbush / flatbush / wkx). Run 2026-10-22T08:01:08.571Z on AMD Ryzen 7 260 w/ Radeon 780M Graphics · commit a1fffdc — regenerate with bun run bench:competitors (optional devDependencies: bun run bench:competitors:setup).

fp = answer fingerprint vs bun-geos at the same size (✓ agree / ✗ differs — read the engine's note before trusting a ✗ row, e.g. turf's buffer projects geographically by design). × vs bun-geos compares each engine's variant against the reference's BEST variant at that size; ratios within [0.9, 1.1] render as ≈ (the cross-process noise floor). Speedups are same-machine, same-input numbers.

Engines

engine version boundary status
bun-geos GEOS 3.14.1-CAPI-1.20.5 / bun-geos 0.1.0 native-ffi ran
geos-wasm geos-wasm / 3.13.0-CAPI-1.19.0 wasm ran
jsts 2.12.1 pure-js ran
turf @turf/turf 7.4.0 pure-js ran
polygon-clipping polygon-clipping 0.15.7 pure-js ran
rbush rbush 4.0.1 pure-js ran
flatbush flatbush 4.6.2 pure-js ran
wkx wkx 0.5.0 pure-js ran
shapely shapely 2.2.0 / GEOS 3.14.1 ctypes ran
geos-cpp geos 3.14.1-CAPI-1.20.5 c-direct ran

overlay

size engine variant ms geoms/s × vs bun-geos fp note
1000 bun-geos singular 19.7 50,707 — — reference column — native FFI to GEOS 3.14
1000 bun-geos batch 19.8 50,560 — — reference column — native FFI to GEOS 3.14
1000 geos-cpp singular 18.8 53,137 ≈ ✓ GEOS C API called directly — zero boundary
1000 geos-wasm singular 66.9 14,946 3.39× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
1000 jsts singular 47.6 20,990 2.42× ✓ JTS port in pure JS — the strongest JS-native engine
1000 polygon-clipping singular 542.7 1,843 27.52× ✓ Martinez-family clipping — overlay ops only
1000 shapely singular 21.6 46,356 ≈ ✓ shapely vectorized ufuncs = the batch-layer parallel
1000 shapely batch 19.0 52,683 ≈ ✓ shapely vectorized ufuncs = the batch-layer parallel
1000 turf singular 3862.8 259 195.87× ✓ web-mapping algorithm ports — GeoJSON-native, planar CRS not enforced
10000 bun-geos singular 197.8 50,547 — — reference column — native FFI to GEOS 3.14
10000 bun-geos batch 194.7 51,374 — — reference column — native FFI to GEOS 3.14
10000 geos-cpp singular 187.7 53,278 ≈ ✓ GEOS C API called directly — zero boundary
10000 geos-wasm singular 623.3 16,043 3.20× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
10000 jsts singular 340.9 29,334 1.75× ✓ JTS port in pure JS — the strongest JS-native engine
10000 polygon-clipping singular 5642.3 1,772 28.99× ✓ Martinez-family clipping — overlay ops only
10000 shapely singular 217.5 45,968 1.12× ✓ shapely vectorized ufuncs = the batch-layer parallel
10000 shapely batch 189.1 52,875 ≈ ✓ shapely vectorized ufuncs = the batch-layer parallel
10000 turf singular 44032.9 227 226.21× ✓ web-mapping algorithm ports — GeoJSON-native, planar CRS not enforced
100000 bun-geos singular 1381.7 72,374 — — reference column — native FFI to GEOS 3.14
100000 bun-geos batch 1347.2 74,228 — — reference column — native FFI to GEOS 3.14
100000 geos-cpp singular 1300.9 76,868 ≈ ✓ GEOS C API called directly — zero boundary
100000 geos-wasm singular 3670.5 27,244 2.72× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
100000 shapely singular 1541.4 64,876 1.14× ✓ shapely vectorized ufuncs = the batch-layer parallel
100000 shapely batch 1316.8 75,942 ≈ ✓ shapely vectorized ufuncs = the batch-layer parallel

union

size engine variant ms geoms/s × vs bun-geos fp note
100 turf unary 15.9 6,289 — — pairwise fold (no cascaded union); 100-tile ladder cap
961 bun-geos coverage-safe 4.2 229,959 — — coverage-safe = validate+union (GEOSCoverageIsValid first — CoverageUnion is silently wrong on non-coverages); coverage-raw = union only, the parity row vs Shapely/cpp
961 bun-geos coverage-raw 0.5 1,983,578 — — coverage-safe = validate+union (GEOSCoverageIsValid first — CoverageUnion is silently wrong on non-coverages); coverage-raw = union only, the parity row vs Shapely/cpp
961 bun-geos unary 9.5 101,672 — — coverage-safe = validate+union (GEOSCoverageIsValid first — CoverageUnion is silently wrong on non-coverages); coverage-raw = union only, the parity row vs Shapely/cpp
961 bun-geos auto 4.1 236,592 — — coverage-safe = validate+union (GEOSCoverageIsValid first — CoverageUnion is silently wrong on non-coverages); coverage-raw = union only, the parity row vs Shapely/cpp
961 geos-cpp unary 9.4 102,101 19.43× ✓ includes per-repeat clone + collection build (C-API input contract)
961 geos-cpp coverage-raw 0.4 2,153,284 ≈ ✓ includes per-repeat clone + collection build (C-API input contract)
961 geos-wasm unary 22.9 41,959 47.27× ✓ coverage-raw is union-only (no validity pre-check); includes per-repeat clone + collection build (C-API input contract)
961 geos-wasm coverage-raw 1.4 709,646 2.80× ✓ coverage-raw is union-only (no validity pre-check); includes per-repeat clone + collection build (C-API input contract)
961 jsts unary 60.2 15,974 124.18× ✓ Geometry.union() on a prebuilt MULTIPOLYGON (no coverage strategy in JSTS)
961 shapely unary 9.8 98,531 20.13× ✓ coverage-raw = union only (coverage_union_all does NO validity pre-check)
961 shapely coverage-raw 0.5 2,088,486 ≈ ✓ coverage-raw = union only (coverage_union_all does NO validity pre-check)

prepared-scan

size engine variant ms geoms/s × vs bun-geos fp note
10000 bun-geos unprepared 18.1 552,202 — — reference column — native FFI to GEOS 3.14
10000 bun-geos prepared 0.7 15,148,853 — — reference column — native FFI to GEOS 3.14
10000 bun-geos prepared+batch 0.8 12,744,698 — — reference column — native FFI to GEOS 3.14
10000 geos-cpp unprepared 18.1 552,793 27.40× ✓ GEOS C API called directly — zero boundary
10000 geos-cpp prepared 0.5 21,179,979 0.72× ✓ GEOS C API called directly — zero boundary
10000 geos-wasm unprepared 30.7 325,564 46.53× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
10000 geos-wasm prepared 1.4 6,956,459 2.18× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
10000 shapely unprepared 19.8 505,235 29.98× ✓ vectorized; prepare() then auto-used by intersects
10000 shapely prepared 0.5 21,153,500 0.72× ✓ vectorized; prepare() then auto-used by intersects
10000 turf unprepared 23.4 426,878 35.49× ✓ ray-casting point-in-polygon (no prepared mode)
100000 bun-geos unprepared 182.0 549,302 — — reference column — native FFI to GEOS 3.14
100000 bun-geos prepared 5.8 17,125,525 — — reference column — native FFI to GEOS 3.14
100000 bun-geos prepared+batch 7.9 12,645,721 — — reference column — native FFI to GEOS 3.14
100000 geos-cpp unprepared 181.4 551,254 31.07× ✓ GEOS C API called directly — zero boundary
100000 geos-cpp prepared 4.8 20,946,756 0.82× ✓ GEOS C API called directly — zero boundary
100000 geos-wasm unprepared 309.7 322,910 53.03× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
100000 geos-wasm prepared 15.1 6,603,225 2.59× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
100000 shapely unprepared 195.8 510,608 33.54× ✓ vectorized; prepare() then auto-used by intersects
100000 shapely prepared 4.8 21,028,265 0.81× ✓ vectorized; prepare() then auto-used by intersects
100000 turf unprepared 234.2 426,960 40.11× ✓ ray-casting point-in-polygon (no prepared mode)

strtree

size engine variant ms geoms/s × vs bun-geos fp note
100000 bun-geos build 31.5 3,172,852 — — reference column — native FFI to GEOS 3.14
100000 bun-geos query 3.7 — — — reference column — native FFI to GEOS 3.14
100000 bun-geos query-batch 3.6 — — — reference column — native FFI to GEOS 3.14
100000 bun-geos query+fused 21.2 4,719,709 — — reference column — native FFI to GEOS 3.14
100000 flatbush build 8.1 12,323,280 2.28× ✓ static packed index (index angle only, immutable data)
100000 flatbush query 3.8 — ≈ ✓ static packed index (index angle only, immutable data)
100000 geos-cpp build 14.1 7,097,898 3.96× ✓ build includes the build-forcing first query (C API has no explicit build)
100000 geos-cpp query 1.7 — 0.47× ✓ build includes the build-forcing first query (C API has no explicit build)
100000 jsts build 24.3 4,110,433 6.84× ✓ JTS port in pure JS — the strongest JS-native engine
100000 jsts query 7.7 — 2.16× ✓ JTS port in pure JS — the strongest JS-native engine
100000 rbush build 14.7 6,823,395 4.12× ✓ dynamic R-tree specialist (index angle only)
100000 rbush query 7.8 — 2.20× ✓ dynamic R-tree specialist (index angle only)
100000 shapely build 15.7 6,370,636 4.41× ✓
100000 shapely query 2.8 — 0.79× ✓

ingest

size engine variant ms geoms/s × vs bun-geos fp note
20000 bun-geos wkt 45.5 439,823 — — reference column — native FFI to GEOS 3.14
20000 bun-geos geojson 34.5 579,577 — — reference column — native FFI to GEOS 3.14
20000 bun-geos wkb 11.0 1,824,722 — — reference column — native FFI to GEOS 3.14
20000 bun-geos wkb-batch 10.9 1,832,717 — — reference column — native FFI to GEOS 3.14
20000 geos-cpp wkt 23.9 837,089 2.19× ✓ GEOS C API called directly — zero boundary
20000 geos-cpp wkb 3.5 5,701,061 0.32× ✓ GEOS C API called directly — zero boundary
20000 geos-wasm wkt 166.6 120,050 15.27× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
20000 geos-wasm wkb 19.4 1,031,749 1.78× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
20000 jsts wkt 55.1 363,080 5.05× ✓ the jsts npm bundle ships no WKB codecs — WKT ingest only
20000 shapely wkt 72.0 277,637 6.60× ✓ per-item loop vs vectorized ufunc
20000 shapely wkt-batch 34.2 585,419 3.13× ✓ per-item loop vs vectorized ufunc
20000 shapely wkb 35.6 561,153 3.27× ✓ per-item loop vs vectorized ufunc
20000 shapely wkb-batch 4.8 4,156,553 0.44× ✓ per-item loop vs vectorized ufunc
20000 wkx wkt 27.4 728,898 2.51× ✓ pure-JS WKB/WKT codec (ingest angle only)
20000 wkx wkb 3.6 5,618,536 0.33× ✓ pure-JS WKB/WKT codec (ingest angle only)
20000 wkx wkt-write 17.1 1,169,369 1.57× ✓ pure-JS WKB/WKT codec (ingest angle only)
20000 wkx wkb-write 4.5 4,476,471 0.41× ✓ pure-JS WKB/WKT codec (ingest angle only)

makevalid

size engine variant ms geoms/s × vs bun-geos fp note
1000 bun-geos makevalid 38.1 26,277 — — reference column — native FFI to GEOS 3.14
1000 geos-cpp makevalid 40.8 24,494 ≈ ✓ GEOS C API called directly — zero boundary
1000 geos-wasm makevalid 96.5 10,366 2.53× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
1000 shapely makevalid 39.6 25,233 ≈ ✓

buffer

size engine variant ms geoms/s × vs bun-geos fp note
100 bun-geos buffer 14.6 6,849 — — reference column — native FFI to GEOS 3.14
100 geos-cpp buffer 12.2 8,214 0.83× ✓ GEOS C API called directly — zero boundary
100 geos-wasm buffer 18.5 5,396 1.27× ✓ GEOS 3.13 via Emscripten — raw C API, manual heap plumbing
100 jsts buffer 17.9 5,575 1.23× ✓ JTS port in pure JS — the strongest JS-native engine
100 shapely buffer 12.6 7,948 0.86× ✓
100 turf buffer 84.1 1,189 5.76× ✗ geographic projection built in — planar fingerprint mismatch expected

Failures & skips

  • geos-wasm strtree@100000: GEOSSTRtree_query callback binding unusable (call_indirect to a signature that does not match (evaluating '$(A)(g, I, B)'))
  • jsts prepared-scan: skipped — op not workable against the installed package's API
Powered by Cirth