Repository navigation
Description
Geoawesome's benchmark of RustGIS 0.4.0 against QGIS 3.34 and plain Python (Sebastian Walczak, 31 October 2026) found fast start-up, file input and output, and scripting, but slower buffering, raster processing and drawing. This issue tracks those gaps. The article doesn't link its notebook, so its numbers below can't be rerun yet. It used Windows, with 16 logical cores and 33.6 GB of RAM.
| Test | RustGIS 0.4.0 | QGIS | Python | Status on main |
|---|---|---|---|---|
| Buffer 100,000 points by 5 km, GeoPackage to GeoPackage | 11.9 s | 6.98 s | 3.92 s | Likely cause found, step 1 |
| Slope, 8.9 million cell DEM | 4.03 s | 0.82 s | 1.41 s | #1130 landed after 0.4.0, not re-measured |
| Draw 100,000 buildings, zoomed to 4 × 3 km | 0.89 s | 0.26 s | 0.47 s | #1065 |
| Render a DEM from a COG instead of a plain GeoTIFF | about 1.1× faster | about 1.6× faster | not given | Step 3 |
| IDW, 100,000 points, 0.01° grid | refused as "too much work" | not given | not given | Step 4 |
Plan
1. Quadratic Dataset::push (small, do first)
The article found buffer time growing about 26× from 10,000 to 100,000 features and placed the cause in the buffer step. Dataset::push (crates/data/src/lib.rs) scans every feature for the largest id on each insert, so building an output of n features makes about n²/2 comparisons. push_next already avoids this for readers, and its comment names the problem. buffer in crates/analysis/src/tools.rs calls push once per feature.
Timing gp.run buffer alone with the PyPI 0.4.0 wheel (Linux, one run each, points read from GeoJSON, distance 5):
| Features | Time |
|---|---|
| 1,000 | 0.004 s |
| 10,000 | 0.04 s |
| 30,000 | 0.35 s |
| 100,000 | 8.81 s |
100,000 features means about 5 billion comparisons, which fits that time, but no patched build has confirmed it yet. Other tools that build their output with push should scale the same way.
- Stop
pushscanning for tool outputs, keeping ids unique when a dataset's ids aren't in order - Add a test or benchmark that catches quadratic growth, and re-measure Buffer
2. Raster speed (already planned in #1111)
The article found slope time proportional to raster size, but five to seven times slower than GDAL at every size.
- Re-measure slope and hillshade with perf(analysis): process terrain gradients by row #1130 against
gdaldem, then update Tracking: large data (layers over 2 million features, slow hillshade and GeoTIFF writing) #1111 step 4 - Threads on the desktop: Tracking: large data (layers over 2 million features, slow hillshade and GeoTIFF writing) #1111 step 6
3. Drawing what's in view
- Read only the view's features when drawing a layer read in place: [Feature]: Layers read in place are read and projected in full twice on every render #1065. The draw pass still calls
visit_chunks(None, …)throughvisit_complete(crates/render/src/lib.rs). - Measure the same zoomed view on a loaded layer, since the article doesn't say which kind of layer it drew.
- Draw local GeoTIFF raster layers from the overview that suits the zoom.
geotiff::readonly picks an overview when the image won't fit underMAX_SAMPLES. It doesn't pick one for the zoom, which may explain the small COG gain.
4. IDW on large grids
- Decide whether
MAX_IDW_WORK(400 million cell-point pairs incrates/analysis/src/rasterops.rs) should stay. The refusal is explicit and RustGIS was fastest at 0.02° (7.08 s against 9.30 s for QGIS), so this is about reach rather than speed. Options are a search radius or an index that lets each cell skip distant points.
5. Follow-up
- Correct
docs/comparison/qgis.md, which still calls PostGIS read only and says edits aren't written back.docs/comparison/index.mdalready says PostGIS and GeoPackage tables write back on desktop (feat(postgis): a PostGIS tab to connect, edit and save tables, refusing conflicts #671, after 0.4.0). - Rerun the article's tests on the next release, with its notebook if the author shares it, and post the results here.
Out of scope
The article's other limits are product decisions and aren't speed work: no full PROJ for vectors, no versioned or multi-user editing, and the size of the plugin registry.
Related
#1111 covers large data and hillshade and GeoTIFF writing, #1142 Clip and Select by Location, #1067 drawing on several cores, #1047 lag on big data, and #1057 per-layer caching.
Activity
pick2510 commentedon Nov 1, 2026
I'll take step 1 (the quadratic
Dataset::push).I reproduced it first with synthetic data. The input was random points over Switzerland as GeoPackages, buffered by 5 km, GeoPackage to GeoPackage. RustGIS was
mainat 1672a54, release CLI, input in lon/lat. QGIS was 4.2.3 (flatpak),native:buffertimed inside one QGIS Python process, input in UTM 32N, 5 segments. The machine is a Xeon Silver 4114 on Linux, two runs each (they agreed within a few percent).pushwithout the id scanThe RustGIS columns subtract start-up and reading (0.03 to 0.1 s). Writing the 100k output takes about 0.9 s more. GeoJSON input gives the same times. The "without the id scan" column is a throwaway build where
pushtakes the last id plus one, only to confirm the cause.Plan for the fix: keep the largest id in
Featuresalongside the rows.pushupdates it, and any other mutable access to the rows clears it, so the nextpushscans once. Ids stay unique when they aren't in order, and none of the ~44pushcallers change. I'll add a test that catches quadratic growth and re-measure Buffer.pick2510 commentedon Nov 1, 2026
Update on Buffer. Step 1 landed in #1245, and #1248 takes on the rest of the gap.
#1245 (merged):
Featureskeeps the largest id, soDataset::pushno longer searches every feature. Buffer on 100,000 points went from 24.2 s to 10.1 s.#1248 (open): almost all of the remaining time was geodesic solves, about 160 for each point's 33-vertex circle. The PR makes three changes:
The output is byte for byte the same as
main, compared as WKB on 30,000 points and on a global mix of points, lines and polygons at 0.5, 5, 200, 2,000 and 5,000 km.Same setup as above: random points, buffered by 5 km, GeoPackage, release CLI, Xeon Silver 4114 with 40 threads, load included, best of two runs. QGIS is 4.2.3
native:bufferin UTM 32N, read and write included.mainmainwith #1245So with both, Buffer on 100,000 points is about 75 times faster than before. On one core it is about 2.8 times faster than QGIS, and on 40 threads about 18 times faster. The browser build stays on one thread.
Writing the 100,000-feature GeoPackage takes about 0.9 s more on top. That is now the larger part, and it isn't covered by either PR.
perf(analysis): geodesic buffers take each vertex back once and look …
pick2510 commentedon Nov 1, 2026
Since #1241 merged, the article's Buffer test no longer runs: Buffer refuses it. #1241 brings two changes:
A 128-edge circle has 129 coordinates, so the budget allows about 15,500 point buffers, as #1241's description says. Measured on
mainat 95376b7 (random points, 5 km, release CLI):Before #1241, with #1245 and #1248, 100,000 points took 0.32 s on 40 threads and 2.1 s on one core, against QGIS 4.2.3's 5.8 s (table above). RustGIS 0.4.0 took 11.9 s in the article. The 4× vertices also cost time below the limit: 10,000 points on one core went from 0.24 s to 0.72 s. Those two figures come from different builds, so they are approximate.
The budget looks stricter than memory needs:
Some options:
Which way to go is a call for #1241's design, so I'm only reporting it here. #1254 (step 1 of #1252) keeps the budget exactly as it is.
Mavrick-F commentedon Nov 2, 2026
Thanks for reproducing this and spelling out the regression. I’ll take the buffer-budget follow-up: keep the finer arcs and conservative per-feature/dissolve limits, give ordinary buffers a byte budget, and process them in bounded batches. I’ll check the 100,000-point case and peak memory, including the distance-selection path.
Mavrick-F commentedon Nov 2, 2026
#1305 is merged. Ordinary Buffer now uses a retained-storage byte budget separately from per-feature complexity, restoring the 100,000-point workload while keeping the finer arcs. Dissolve All retains its cumulative vertex guard. Distance selection also accounts for retained storage and bounds actual candidate checks for larger selector sets, preserving small-set and zero-distance behavior. The PR includes the reproduction, resource measurements and validation results.