Skip to content

Benchmarks

There are two separate benchmark setups with different purposes:

Continuous benchmark — source and results at https://github.com/ringsaturn/tz-benchmark, visualized at https://ringsaturn.github.io/tz-benchmark/. Runs automatically in GitHub Actions on each release for cross-package comparison. Because GitHub Actions runners have different hardware than a developer machine, the absolute numbers differ from local runs, but the relative trends between packages are what matters here. The dated snapshots under snapshot/ are a separate thing: those are captured locally on the same Apple M3 Max as the tables below, so their absolute numbers are directly comparable to this page.

Local benchmark — the tables below were measured on an Apple MacBook Pro with Apple M3 Max. These give a more representative picture of real-world latency on modern hardware.

Methodology

Each finder is initialized once and reused for all queries, matching the recommended production pattern. Queries use a representative sample of global city coordinates plus intentional edge-case border points.

Two different notions of memory appear below, and they are not interchangeable. The Go tables report retained memory — what the finder still holds once it is ready to serve queries. The Rust and Python tables report peak RSS — the high-water mark reached while loading, which is several times larger because building a finder decodes the whole .pb dataset into an intermediate representation before discarding it, and freeing memory does not return the pages to the kernel. Do not read a Go figure and a Rust figure as the same measurement. SeeHow much memory does tzf use? for both figures measured side by side on the same runs.

Go (tzf v1.2.3)

TargetDatasetScenarioMedian (ns)p99 (ns)Approx throughput (ops/s)Retained (MiB)
DefaultFindertopology-simplified + preindexedge case · GetTimezoneName625.02250.01083.8K31.90
FuzzyFinderpreindexedge case · GetTimezoneName250.0542.03216.5K2.40
Findertopology-simplifiededge case · GetTimezoneName334.01667.02145.0K29.70
FullFinderfull-precision + preindexedge case · GetTimezoneName709.02875.01111.7K155.30
Finderfull-precisionedge case · GetTimezoneName416.02709.01652.6K153.00
DefaultFindertopology-simplified + preindexrandom world cities · GetTimezoneName208.01208.03283.0K31.90
FuzzyFinderpreindexrandom world cities · GetTimezoneName208.0542.03717.5K2.40
Findertopology-simplifiedrandom world cities · GetTimezoneName292.02208.02058.0K29.70
FullFinderfull-precision + preindexrandom world cities · GetTimezoneName208.01375.03147.6K155.30
Finderfull-precisionrandom world cities · GetTimezoneName333.01959.01993.6K153.00
Findertopology-simplified + GridIndexrandom world cities · GetTimezoneName250.01667.02387.2K29.70
Findertopology-simplified (no GridIndex)random world cities · GetTimezoneName2292.04375.0471.7K24.00
DefaultFindertopology-simplified + preindexrandom world cities · GetTimezoneNames625.03833.0971.8K31.90
FuzzyFinderpreindexrandom world cities · GetTimezoneNames209.0583.03534.8K2.40
Findertopology-simplifiedrandom world cities · GetTimezoneNames583.02833.01277.3K29.70
FullFinderfull-precision + preindexrandom world cities · GetTimezoneNames709.03292.01059.0K155.30

Rust (tzf-rs v1.3.6)

Topology-Simplified (bundled) / Random Cities

TargetDatasetScenarioMedian estimate (µs)Approx throughput (ops/s)Avg init peak RSS (MiB)
Findertopology-simplifiedYStripes only0.56981,755,03369.72
Findertopology-simplifiedNo index4.9164203,40142.46
DefaultFindertopology-simplified + preindexYStripes only0.30403,289,36582.10
DefaultFindertopology-simplified + preindexNo index5.0438198,26358.11

Topology-Simplified (bundled) / Edge Cities (FuzzyFinder misses)

TargetDatasetScenarioMedian estimate (µs)Approx throughput (ops/s)
FuzzyFinderpreindexFuzzyFinder miss0.15646,393,044
DefaultFinder (YStripes)topology-simplified + preindexDefaultFinder (YStripes) fallback0.62561,598,338
Findertopology-simplifiedYStripes0.44212,261,676
Findertopology-simplifiedNo index4.9164203,401
DefaultFindertopology-simplified + preindexYStripes0.60691,647,718
DefaultFindertopology-simplified + preindexNo index5.0438198,263

Full-Precision (full)

TargetDatasetScenarioMedian estimate (µs)Approx throughput (ops/s)Avg init peak RSS (MiB)
Finder (full)full-precisionYStripes only1.2227817,862314.59
Finder (full)full-precisionNo index43.052023,228157.02
DefaultFinder (full)full-precision + preindexYStripes only0.55271,809,136323.58
DefaultFinder (full)full-precision + preindexNo index7.4823133,649171.44

Python (tzfpy v1.3.2)

tzfpy is a PyO3 binding over tzf-rs. The benchmark uses pytest-benchmark and measures a single get_tz() call (random coordinate, topology-simplified dataset). Results from Apple MacBook Pro with Apple M3 Max.

Index modeMedian (µs)Mean (µs)Throughput (Kops/s)Peak RSS
Default (YStripes enabled)0.65330.67111490.1~70.5 MB
No YStripes (_TZFPY_DISABLE_Y_STRIPES=1)1.64101.6548604.3~57.5 MB

Per-call overhead is comparable to the raw Rust figures; the difference from tzf-rs numbers reflects the Python → Rust FFI cost via PyO3.

Key observations

  • YStripes substantially reduces polygon lookup latency. For Rust Finder, the full-precision median falls from 43.0520 µs to 1.2227 µs, a 35.2× speedup. On the topology-simplified dataset it falls from 4.9164 µs to 0.5698 µs, an 8.6× speedup.
  • DefaultFinder is the strongest general-purpose Rust option. Its median is 0.3040 µs on topology-simplified data and 0.5527 µs on full-precision data. The preindex adds about 9 to 12 MiB of init peak RSS over the corresponding YStripes-enabled Finder.
  • FuzzyFinder is useful as a fast path with a fallback. A miss completes in 0.1564 µs, while DefaultFinder resolves the same edge-city workload through its YStripes fallback in 0.6256 µs. Standalone use remains suitable only when queries are known to stay away from timezone borders.
  • Python benefits significantly from YStripes. The tzfpy median falls from 1.6410 µs to 0.6533 µs, and throughput rises from 604.3 to 1490.1 Kops/s, about 2.5×.
  • Full-precision data has a clear memory cost. With YStripes enabled, moving from topology-simplified to full-precision data adds about 241 to 245 MiB of init peak RSS in Rust. In Go, retained memory rises from about 30 MiB to about 153 to 155 MiB. These are the two different bases described in Methodology, so the Rust and Go increases are not directly comparable.
  • Init peak is not what tzf costs to keep running. The Rust peak RSS figures above overstate steady state by roughly 2× to 3×: in the 2026-07-26 snapshot, measured on the same machine, DefaultFinder on topology-simplified data peaks at 77.0 MiB while retaining 36.3 MiB, and Finder peaks at 48.0 MiB while retaining 20.7 MiB. Size a container for the peak; budget long-running cost from the retained figure.
Last updated on