コンテンツにスキップ

Finder の選択

tzf v2 のどの Finder も同じデータセットから結果を返します。異なるのは常駐メモリ、オープン時間、そしてプレインデックスがその地点をカバーしていない場合のクエリレイテンシです。このページでは、それらの実測値と、そこから導かれる配置パターンをまとめます。

言語別の選択

状況GoRustPython
バックエンドサービス、通常のコンテナNewDefaultFinder()DefaultFinder::new()get_tz()(モジュール自体が共有のデフォルト Finder)
メモリが数十 MB、またはファイルシステムなしNewEmbeddedFinder()EmbeddedFinder::new()利用不可
境界から約 111 m 以内で正確な結果が必要NewFullFinder()DefaultFinder::new_full()(git 限定の full feature)利用不可
ディスクやオブジェクトストレージから読んだ独自のバイト列NewFinderFromTZB / NewFinderFromTZMDefaultFinder::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 MB298 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 がページキャッシュ経由でプロセス間共有可能な読み取り専用マッピングであり、残りがヒープです。
  • クエリ: ランダムな世界都市に対する GetTimezoneName 1 回分です。インプレース方式がマイクロ秒単位になるのは、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.Fileio.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-distNewDefaultFinder() が読み込むため 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-distlite.tzb + lite.tzm + full.tzbdeflate 後 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.tzb2.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 以内に位置する可能性があり、かつ正確な名前が必要な場合です。ジオフェンス、課金、管轄区域の判定などが含まれます。実測された変位の表はよくある質問を参照してください。

最終更新日