Finder の選択
tzf v2 のどの Finder も同じデータセットから結果を返します。異なるのは常駐メモリ、オープン時間、そしてプレインデックスがその地点をカバーしていない場合のクエリレイテンシです。このページでは、それらの実測値と、そこから導かれる配置パターンをまとめます。
言語別の選択
| 状況 | Go | Rust | Python |
|---|---|---|---|
| バックエンドサービス、通常のコンテナ | NewDefaultFinder() | DefaultFinder::new() | get_tz()(モジュール自体が共有のデフォルト Finder) |
| メモリが数十 MB、またはファイルシステムなし | NewEmbeddedFinder() | EmbeddedFinder::new() | 利用不可 |
| 境界から約 111 m 以内で正確な結果が必要 | NewFullFinder() | DefaultFinder::new_full()(git 限定の full feature) | 利用不可 |
| ディスクやオブジェクトストレージから読んだ独自のバイト列 | NewFinderFromTZB / NewFinderFromTZM | DefaultFinder::from_tzb / EmbeddedFinder::from_tzb | 利用不可 |
配置マトリクス
Apple M3 Max で 2026c データセットを対象に測定した値です。シングルコアのオープン時間の列は CPU クォータが 1 コア未満の Pod を、16 コアの列は制限のないノートパソコンやノードを想定しています。
| 方式 | Go のコンストラクタ | オープン時間(16 コア / 1 コア) | 常駐 | クエリ |
|---|---|---|---|---|
lite .tzm メモリイメージ | NewDefaultFinder() | 7.7 ms / 28 ms | ヒープ約 12 MB + 読み取り専用データ 10 MB | 298 ns |
lite .tzb をインプレースで参照 | NewEmbeddedFinder() | 1.7 ms / 1.7 ms | 約 3 MB | 約 6 µs(プレインデックスヒット時は p50 542 ns) |
lite .tzb を展開 | NewFinderFromTZB(lite) | 19.9 ms / 41 ms | 約 27 MB | 約 290 ns |
full .tzb を展開 | NewFullFinder() | 78.5 ms / 214 ms | 約 145 MB | 約 300 ns |
| full protobuf(v1、参考) | — | 288 ms / 344 ms | 約 153 MB | 約 290 ns |
その後に導入されたバイト列ベースのデコード高速パスにより、展開方式のオープン時間はさらに短縮されました。同一マシンでのウォーム状態の 5 回中最良値は、lite 展開が 20.2 ms から 16.8 ms、full 展開が 72.7 ms から 63.5 ms、lite .tzm が 7.5 ms から 6.9 ms です。
各列の意味は次の通りです。
- オープン時間: 一度だけ発生するコストです。すべての Finder は並行利用が安全なため、プロセスは 1 つの Finder を構築して再利用します。オープン時間は起動レイテンシ、スケールトゥゼロの関数、CLI ツールに影響します。
- 常駐: クエリを処理している間にプロセスが保持する量です。
.tzmメモリイメージでは、このうち約 10 MB がページキャッシュ経由でプロセス間共有可能な読み取り専用マッピングであり、残りがヒープです。 - クエリ: ランダムな世界都市に対する
GetTimezoneName1 回分です。インプレース方式がマイクロ秒単位になるのは、point-in-polygon にフォールバックするたびに圧縮ファイルからジオメトリをデコードするためです。
ファイルシステムがない環境、メモリが少ない環境
NewEmbeddedFinder() は lite .tzb をインプレースで読みます。ファイルバイト列に加えて 1 KB 未満のヒープ、合計約 3 MB で、ロード時のデコードはありません。クエリはアロケーションを行いません。該当するケースは、組み込み環境、1.7 ms のオープン時間が 6 µs のテールレイテンシより重視されるスケールトゥゼロの関数、そしてメモリ上限が低いプロセスです。
バイト列がバイナリに埋め込まれていない場合(ディスク上のファイル、mmap した領域、オブジェクトストレージ上のオブジェクトなど)は、ファイル全体をメモリに読み込む代わりに実験的な x パッケージを使用します。
f, err := os.Open("lite.tzb")
if err != nil {
panic(err)
}
info, err := f.Stat()
if err != nil {
panic(err)
}
finder, err := x.NewFinderFromTZBReaderAt(f, info.Size())*os.File は io.ReaderAt を満たし、mmap のラッパーも同様です。ファイルはオープン時に一度検証され、その後はクエリが必要とするバイトだけを読みます。ReaderAt へのアクセスはアロケーションのないクエリパスを維持するために内部で直列化されるため、スループットはコア数に比例しません。並行スループットがヒープサイズより重要な場合は .tzm メモリイメージが該当します。
x パッケージはモジュールのセマンティックバージョニングの約束の対象外です。マイナーバージョンの更新で API が変更または削除される場合があります。
マルチコアノードと cgroup クォータ下の Pod
.tzm のローダーはオープン時に YStripes ポリゴンインデックスを並列に再構築します。16 コアではこのステップに約 5 ms かかります。CPU クォータが 1 コア未満の場合、上記の 7.7 ms と 28 ms の差の大部分はこのステップによるものです。full データセットでは、同じステップが 1 コアで 158 ms かかります。
Pod の CPU クォータが 1 コア未満で、起動レイテンシが SLO の対象となる場合の選択肢は次の通りです。
NewEmbeddedFinder()。1.7 ms のオープン時間はコア数に依存しません。NewDefaultFinder()を使用し、readiness プローブが ready を返す前に Finder を構築します。- プラットフォームが対応している場合は、起動時のみ CPU クォータを引き上げます。
クエリレイテンシはコア数に依存しません。
ローカルでの .tzm 変換
tzf-dist は NewDefaultFinder() が読み込むため lite.tzm を配布しています。full.tzm は配布していません。このファイルは 63.6 MB であり、full.tzb の 13.77 MB と比較すると大きいためです。M プロファイルは、それを使用するホスト上で生成します。
変換は protobuf を使用せずに実行され、M プロファイルをソースからエンコードした場合とバイト単位で同一の出力を生成します。
go run github.com/ringsaturn/tzf/v2/cmd/tzb2tzm@latest -o full.tzm full.tzb生成結果は NewFinderFromTZM(data) で読み込むか、ファイルを mmap してマップされたバイト列を渡します。リングのストレージがこれらのバイト列をインプレースで参照するため、バイト列は Finder の生存期間中、有効かつ変更されない状態を保つ必要があります。リトルエンディアンのホストで 8 バイト境界に整列したスライスであれば、ローダーはゼロコピーのビューを取得します。整列していない場合やビッグエンディアンのホストでは、一度だけデコードしたコピーにフォールバックし、メモリはおよそ 2 倍になります。
配布サイズ
| 配布経路 | 内容 | サイズ |
|---|---|---|
Go モジュール(tzf-dist) | lite.tzb + lite.tzm + full.tzb | deflate 後 2.55 + 6.35 + 10.39 MB、合計約 19.3 MB |
| Rust crate(crates.io) | lite.tzb のみ | 約 4 MB |
Rust crate(git、full feature) | full.tzb | 約 14 MB |
Python wheel(tzfpy) | 拡張モジュール内の lite.tzb | 2.76 MB(v1 は 4.31 MB) |
Go モジュールのセットは v1 の protobuf 2 ファイル構成(deflate 後約 16 MB)より約 3 MB 大きくなっています。lite データセットの両プロファイルを含むためです。Python の wheel は protobuf のデコードパスが削除されたため小さくなりました。
複数結果 API を使用する場面
GetTimezoneName / get_tz_name / get_tz は 1 つの名前を返し、ファジー優先で動作します。タイル検索が大部分のクエリを point-in-polygon なしで解決します。複数結果 API(GetTimezoneNames / get_tz_names / get_tzs)が該当するのは次の場合です。
- 境界の重複: ソースデータには複数のタイムゾーンにカバーされる領域が含まれます。Asia/Shanghai と Asia/Urumqi が共有する領域がこれに当たります。
- 共有境界上の地点: 共有境界上の地点は、接するすべてのポリゴンに属します。海上タイムゾーンの境界は 7.5°、22.5° のような整数分割の経線上にあります。
- ポリゴンによる厳密な結果: 複数結果 API はどの Finder でもプレインデックスを参照せず、すべての候補を評価します。
- インターフェイス上の曖昧性: 単一名 API は最初の一致を返すため、呼び出し側は一意な結果と切り詰められた結果を区別できません。
コストは増加します。tzf-rs 自身の criterion ベンチマーク(Apple M3 Max、lite データセット)では、DefaultFinder のランダム都市検索はポリゴンによる厳密な検索が 178 ns、ファジー優先が 75 ns であり、境界付近では差がさらに大きくなります。
full データセットとの精度差
lite データセットは epsilon 0.001 度のトポロジー対応 Douglas-Peucker 簡略化を適用しており、境界の変位は約 111 m に抑えられます。2026-09-11 のスナップショットでは、lite の Finder は 154,694 件の世界都市のうち 1 件(0.0006%)で完全精度の正解データと異なる結果を返し、その 1 件も UTC オフセットは同じでした。
full データセットが該当するのは、クエリが境界から約 111 m 以内に位置する可能性があり、かつ正確な名前が必要な場合です。ジオフェンス、課金、管轄区域の判定などが含まれます。実測された変位の表はよくある質問を参照してください。