# Performance

> Benchmark results comparing geolith against Planetiler on identical workloads.

## geolith vs Planetiler (Overture + OSM)

geolith was benchmarked head-to-head against [Planetiler](https://github.com/onthegomap/planetiler) (Java) on identical input data and region bounds.

### Test Configuration

| Parameter | Value |
| --- | --- |
| Region | Western India (`67.67,14.39,80.92,24.72`) |
| Max zoom | 15 |
| OSM PBF | `western-zone-latest.osm.pbf` (196 MB) |
| Overture | Buildings + Divisions (hive-partitioned GeoParquet) |
| India boundary | GeoJSON |
| Natural Earth | SQLite |
| Land polygons | Shapefile (EPSG:3857) |
| Water polygons | Shapefile (EPSG:3857) |
| Machine | Apple M2 Max, 96 GB RAM, NVMe SSD |

Both tools processed the same layers: earth, water, boundaries, landuse, roads, transit, buildings, places, pois, and divisions. Planetiler additionally processed daylight landcover (0.3s wall time, negligible impact).

### Results

| Metric | geolith (Rust) | Planetiler (Java) |
| --- | --- | --- |
| **Wall time** | **5:15** | 6:20 |
| **CPU time (user)** | **709s** | 3,343s |
| **CPU utilization** | 297% | 895% |
| **Output size** | **2.0 GB** | 3.4 GB |
| **Peak memory** | **~2 GB** | 25 GB (`-Xmx24g`) |
| **Tiles written** | 1,612,666 | — |

### Summary

- **17% faster** wall time (5:15 vs 6:20)
- **4.7x less CPU** time (709s vs 3,343s)
- **41% smaller** output (2.0 GB vs 3.4 GB)
- **~12x less memory** (~2 GB vs 25 GB heap)

geolith achieves competitive performance with significantly lower resource usage. The smaller output is primarily due to tile content deduplication and MLT columnar encoding (50–80% smaller than MVT).

### Phase Breakdown (geolith)

| Phase | Duration | Details |
| --- | --- | --- |
| Phase 1: Overture GeoParquet | ~52s | 6.4M features read, 66.3M tile features |
| Phase 1: OSM PBF (2-pass) | ~25s | 31.8M nodes, 3.8M features |
| Phase 1: Natural Earth | <1s | 18,939 features from 12 tables |
| Phase 1: Land polygons | ~78s | 833K polygons from shapefile |
| Phase 1: Water polygons | ~90s | 14.5K polygons from shapefile |
| Phase 1: India boundary | ~2s | 1 GeoJSON feature |
| Phase 2: External sort | <1s | 34 LZ4-compressed chunks, k-way merge |
| Phase 3: Tile encode | ~50s | 1,612,666 tiles, MLT columnar encoded |
| **Total** | **5:15** | 9.5M input features, 77.5M tile features |

### geolith CLI

```bash
geolith \
    --data ./overture-data/ \
    --osm-pbf western-zone-latest.osm.pbf \
    --natural-earth natural_earth_vector.sqlite \
    --land-polygons ./land-polygons-split-3857/ \
    --water-polygons ./water-polygons-split-3857/ \
    --india-boundary india-boundary.geojson \
    --output western-zone.pmtiles \
    --bbox 67.67,14.39,80.92,24.72 \
    --max-zoom 15 \
    --node-cache
```

### Planetiler CLI

```bash
java -Xmx24g -jar protomaps-basemap-HEAD-with-deps.jar \
    --osm-path=western-zone-latest.osm.pbf \
    --output=western-zone-planetiler.pmtiles \
    --bounds=67.67,14.39,80.92,24.72 \
    --buildings_source=overture \
    --overture_buildings_path=./overture-buildings/ \
    --divisions_source=overture \
    --overture_divisions_path=./overture-divisions/ \
    --india_boundary_path=india-boundary.geojson \
    --nodemap-type=sparsearray --nodemap-storage=mmap \
    --download --force
```

---

## geolith vs Planetiler (OSM Only)

A second benchmark isolates OSM-only performance, removing Overture data entirely. This tests the core tile pipeline: OSM PBF reading, Natural Earth, shapefiles, and India boundary.

### Test Configuration

| Parameter | Value |
| --- | --- |
| Region | Western India (`67.67,14.39,80.92,24.72`) |
| Max zoom | 15 |
| OSM PBF | `western-zone-latest.osm.pbf` (197 MB) |
| Overture | None (`--no-conflate`, empty data directory) |
| Natural Earth | SQLite (923 MB) |
| Land polygons | Shapefile (EPSG:3857) |
| Water polygons | Shapefile (EPSG:3857) |
| India boundary | GeoJSON |
| Machine | Apple M2 Max, 96 GB RAM, NVMe SSD |

Both tools processed the same OSM-derived layers: earth, water, boundaries, roads, buildings, and places. Planetiler additionally processed daylight landcover (0.3s wall time, negligible impact). No Overture themes (divisions, bathymetry, pois) were included.

### Results

| Metric | geolith (Rust) | Planetiler (Java) |
| --- | --- | --- |
| **Wall time** | **3:31** | 2:29 |
| **CPU time (total)** | **587s** | 778s |
| **Peak memory** | **5.4 GB** | 10.9 GB |
| **Output size** | 472 MB | **418 MB** |
| **Tiles written** | 1,612,666 | 1,612,666 |
| **Input features** | 3.7M | 14.7M |
| **Tile features** | 14.6M | — |

### Analysis

Planetiler wins on wall time (**1.4x faster**) in this OSM-only configuration, primarily due to its streaming pipeline that overlaps reading and encoding phases. geolith's 4-phase architecture processes each stage sequentially.

However, geolith shows significant advantages in resource efficiency:

- **25% less CPU** time (587s vs 778s total)
- **~2x less memory** (5.4 GB vs 10.9 GB)
- OSM processing itself (pass 1 + pass 2) took only **47s** — comparable to Planetiler's ~69s

Output sizes are within 13% (472 MB vs 418 MB). The difference is due to geolith including OSM waterway features (rivers, streams, canals) that Planetiler filters differently.

**Parallel shapefile improvement:** With parallel land + water polygon reads, geolith improved from **4:48** → **3:31** wall time — a **27% reduction**. Land and water polygons now overlap on separate OS threads:

- **Land polygons**: ~106s
- **Water polygons**: ~135s (bottleneck)
- **Combined wall time**: ~135s (vs ~223s sequential)

### Phase Breakdown (geolith)

| Phase | Duration | Details |
| --- | --- | --- |
| osmium extract | ~4s | bbox filter (197 MB → 197 MB, region covers entire file) |
| OSM pass 1 (nodes + relations) | ~25s | 31.9M nodes, 128K ways with relation info |
| OSM pass 2 (features) | ~17s | 3.8M features, 68K tile features; 51K water features from OSM |
| Natural Earth | <1s | 18,939 features from 12 tables |
| Land + Water polygons (parallel) | ~135s | 833K land + 14.5K water polygons on separate threads |
| India boundary | ~2s | 1 GeoJSON feature |
| External sort | <1s | 16 LZ4-compressed chunks, k-way merge |
| Tile encode + write | ~26s | 1,612,666 tiles, gzip compressed |
| **Total** | **3:31** | 3.7M input features, 14.6M tile features |

### geolith CLI

```bash
geolith \
    --data ./empty-overture/ \
    --osm-pbf western-zone-latest.osm.pbf \
    --natural-earth natural_earth.sqlite \
    --land-polygons ./land-polygons/ \
    --water-polygons ./water-polygons/ \
    --india-boundary india-boundary.geojson \
    --output western-zone-osm.pmtiles \
    --bbox 67.67,14.39,80.92,24.72 \
    --max-zoom 15 \
    --no-conflate \
    --node-cache
```

### Planetiler CLI

```bash
java -Xmx24g -jar protomaps-basemap-HEAD-with-deps.jar \
    --osm-path=western-zone-latest.osm.pbf \
    --output=western-zone-planetiler-osm.pmtiles \
    --bounds=67.67,14.39,80.92,24.72 \
    --nodemap-type=sparsearray --nodemap-storage=mmap \
    --force
```

---
## Expected Performance

| Dataset | Size | Zoom | Time | Output | Machine |
| --- | --- | --- | --- | --- | --- |
| Regional (western India) | ~196 MB OSM + Overture | 15 | ~5 min | ~2 GB PMTiles | M2 Max, 96 GB RAM |
| Regional (India full) | ~5 GB OSM + Overture | 15 | ~15–25 min | ~5–8 GB PMTiles | M2 Max, 96 GB RAM |
| Planet (all themes) | ~70 GB OSM + ~290 GB Overture | 15 | TBD | TBD | 32 cores, 64 GB RAM, NVMe |

::note
Planet-scale benchmarks are planned. The regional result above demonstrates geolith already outperforms Planetiler on identical workloads.
::

## Bottlenecks

Performance is typically bound by one of three factors:

1. **Disk I/O** — Reading GeoParquet/PBF input and writing temporary sort files. Use NVMe SSDs for the data directory and `--tmpdir`.
2. **CPU** — Feature processing (projection, clipping, simplification) is parallelized via rayon. More cores = faster processing.
3. **Memory** — External sort keeps memory usage bounded. The main memory consumers are the Parquet reader buffers and the OSM node store.

## Tuning Recommendations

| Factor | Recommendation |
| --- | --- |
| Disk speed | NVMe SSD for `--data`, `--tmpdir`, and `--output` paths |
| Thread count | Default (all cores) is usually optimal. Reduce with `--threads` if sharing the machine. |
| Temp directory | Place on the fastest available disk. Avoid network mounts. |
| Max zoom | Each additional zoom level roughly quadruples tile count. See [Output Tuning](/builder/guides/output-tuning). |
| Node cache | Use `--node-cache` to skip OSM pass 1 on subsequent runs with the same PBF. |

## Running Micro-Benchmarks

```bash
# Run the full benchmark suite
cargo bench

# Run a specific benchmark file
cargo bench --bench tile_encode

# Filter by benchmark name
cargo bench -- "encode_tile"
```

Micro-benchmarks use [Criterion.rs](https://bheisler.github.io/criterion.rs/book/) for statistical analysis. Results include confidence intervals and regression detection. Reports are saved to `target/criterion/` with HTML visualizations.
