Parity and speed¶
About how the tests show that the Rust engine makes the same output as the Python engine, how fast it is, and how much memory it uses.
What "the same" means¶
For the style and the page, the same means byte for byte: the same keys in the same order, the same numbers to the last digit.
The Rust engine writes JSON as Python's json.dumps writes it, with its separators, its \uXXXX escapes and its floats
(1e-05, 60.0), and it parses floats exactly.
For the tiles, the same means the same features when decoded: the same ids, properties and geometry. The bytes can differ,
because Python's encoder (mapbox_vector_tile) writes a negative integer as int_value and a float with no fraction as an
integer, and the engine writes sint_value and a double. MapLibre reads the same numbers from both.
The goldens¶
rust/roadstyle-rs/tests/make_goldens.py is the one generator of the reference outputs. It runs Python roadstyle on three areas:
| Area | Roads | Goldens |
|---|---|---|
| Monaco | 13,298 | the style of export_inline, the tiles of export_style, the levels (tags and solve), the color cases, the looks |
| Södermalm (sample) | 4,563 driving edges | the style, the tiles, the levels, with duckOSM's solved levels |
| Tartu | 64,828 | the style, the tiles, the levels of the tags rule |
The tiles are the 3 × 3 tiles around the center at zooms 12, 14 and 15. The files over 1 MB and Tartu's inputs are on the
GitHub pre-release rust-test-data. The repository keeps their SHA-256, and get_goldens.sh checks it.
The Rust tests (cargo test) compare the engine with the goldens: areas.rs for the three areas, colors.rs for the
data-driven color, look.rs for the looks of each profile and palette, levels.rs and style.rs.
The tests with both engines¶
| Test | What it shows |
|---|---|
tests/test_engine_parity.py |
The two engines give the same style, page, export_style files, look and levels on a part of Monaco, case by case: data colors, column names, caps, metre widths, overlays, decks, the full look, views, tiled pages, PMTiles |
ROADSTYLE_ENGINE=rust pytest |
The whole Python suite (561 tests on 2026-10-23) passes with the Rust engine as the default |
tests/test_contract_names.py |
The names of the interaction contract in the code are the names in the reference |
The CI workflow .github/workflows/rust.yml runs on each push to main or rust-engine that changes rust/ or
src/roadstyle/, and on pull requests:
- the job
rust: the goldens,cargo test --release,cargo clippy -D warnings, and the WebAssembly build; - the job
python: it buildsroadstyle._rust, runstests/test_engine_parity.py, then the whole suite withROADSTYLE_ENGINE=rust.
Each night, CI also runs the Tartu goldens, which take longer.
The differences that stay¶
Each difference that the goldens found has a cause and a decision. These stay:
| Difference | Cause | Decision |
|---|---|---|
| The nearest road of a name or arrow slot: 4 slots of Monaco, 10 of Tartu | The two directions of one way have the same line, so the slot is at distance 0 from both. GEOS's tree takes one of them, the engine takes the lower index | Keep: the name and the arrow are the same |
| The solve of Monaco: 8 of 13,298 rows | Python's linear program and the engine's minimum-cost flow take different answers among equal best ones. Both break 0 hard rules | Keep: equal quality by roadstyle's own rules. Södermalm is equal in all rows |
| The bytes of a tile | The two encoders write numbers in different forms (see above) | Keep: the tests compare decoded tiles |
| The corners of the 3D bridge decks | The engine makes the ribbons with its own mitre offsets, not with GEOS | Keep: the same to 1e-6 degrees |
| No Python solve golden for Tartu | Python's solve needs more memory than the test machine has | Keep: Monaco and Södermalm cover the solve |
Speed¶
render_edges from the GeoDataFrame to the HTML text, measured on 2026-10-23 on one machine: an Intel Core i5-13600K
(20 threads), 23 GB of memory, Linux (WSL2), Python 3.11, Rust 1.99.1, the release build.
| Area | Page | Python | Rust | Rust is faster by |
|---|---|---|---|---|
| Tartu (64,828 roads) | inline (the default) | 19.1 s | 6.2 s | 3.1 times |
| Tartu | tiles=True |
147.5 s | 12.2 s | 12 times |
| Monaco, a part (1,373 roads) | the style of export_inline |
0.57 s | 0.12 s | 4.8 times |
| Monaco (osmnx, 849 drive edges) | inline | 0.9 to 1.3 s | 0.3 s | 3 to 4 times |
Memory¶
The peak resident memory of the native program (/usr/bin/time), for the style:
| Area | Roads | Peak | Time |
|---|---|---|---|
| Monaco | 13,298 | 194 MB | 0.55 s |
| Tartu | 64,828 | 1.13 GB | 2.9 s |
Tartu uses about 17 KB for each road. In the browser, the WebAssembly module used 0.65 to 0.77 times the native peak in earlier
measurements. A WebAssembly module has at most 4 GB, so the engine stops at 200,000 roads (MAX_ROADS) with an error. This
limit is an estimate from Tartu, not a measurement of a larger area. Stockholm county (456,813 driving edges) is over the limit.
The memory for each road goes down when the columnar road model of the plan is built (P4). It is not built.
See also: Architecture of the Rust version · Build from source