ripgrep では pbf の中を探せない — Wide Field⑥:osmium で XML にして渡すと148秒、uvp に直接渡すと29.3秒(2回目からは6.8秒)

技術解説

2分半、待ちました。

osmium cat で OpenStreetMap の pbf を XML に展開し、rg に流す。これで1回の検索に148秒です。pbf は地図データの圧縮形式なので、rg にそのまま渡しても中身は見えません。

展開しないと探せない、というのは本当でしょうか。

先に結論: uvp は pbf を直接読みます。osmium cat … -f osm | rg が cold 148.17秒・hot 156.39秒かかるところ、uvp は 1回目(.uwvz 作成込み)29.32秒・2回目 6.79秒・3回目 6.47秒(5.05倍/21.82倍/24.17倍)。件数は 94,979件で一致。ただし pbf を読めるのは有料版の uvp だけで、無料版の uvf は名指しで断ります

対象は japan-latest.osm.pbf 2.29GiB(node 3億1,227万・way 4,469万・relation 22万)。Mac(Apple M4・10コア)・メモリ32GB・外付け USB SSD、相手は osmium 1.19.1 と ripgrep 15.2.0。

uvf・uvp は私が作っている UwView(v1.7.3.6 “Wide Field”・2026年9月28日公開)の検索コマンドです。uvf は無料版、uvp は有料版 UwView Pro のものです。pbf 対応は v1.7.6 相当の実装で、この記事の秒数は v1.7.3.6 の実物を2026年10月2日に測った値です。


先に、できないことと代価を書きます

この回は速さで負けている項目がありません。なので、負けの代わりに限界を先に並べます。

  • 無料版では読めません。 pbf を扱えるのは uvp だけです。uvf に渡すと、テキストとして走査して「1件も無い」と答えるのではなく、名指しで断って終了コード2で終わります。
  • .uwvz の置き場所が要ります。 できあがりは 5.34GiB。元の pbf 2.29GiB の約2.3倍です。圧縮形式を読んで、索引つきの別の圧縮形式に書き直しているので、元より大きくなります。
  • pbf には戻せません。 -extract が出すのは中身の XML(<名前>.osm)です。要素の並べ替えと文字列表の作り直しが要るので、pbf として書き戻すことはしていません。そして先にそう言うようにしました。.pbf という名前で XML を書いて「開けないファイル」を作るほうが悪いからです。
  • 相手は ripgrep 単体ではありません。 rg は pbf を読まないので、比べているのは osmium cat との組み合わせです。つまりこれは「同じ土俵で速い」という話ではなく、土俵が1つ少なくて済むという話です。
  • zlib 以外の blob は断ります。 pbf の中の圧縮は実物ではまず zlib ですが、lzma・lz4・bzip2 の blob もあり得ます。無理に対応せず、当たったときに名指しで断る形にしました。

なぜ rg に渡せないのか

pbf(.osm.pbf)は、OpenStreetMap のデータを protobuf で詰めたバイナリです。XML と同じ内容が、ずっと小さく入っています。実際、ここで使う 2.29GiB の pbf は、XML にすると 51GB 相当になります。

テキストではないので、rg で 東京 を探しても意味のある結果は出ません。だから、これまでは先に展開するのが普通の手順でした。

osmium cat japan-latest.osm.pbf -f osm | rg -n -F '東京'

osmium は OpenStreetMap のデータを扱う定番の道具です。-f osm で XML を標準出力に流し、それを rg が受けます。ファイルには書かないので、ディスクは要りません。

その代わり、毎回ぜんぶ展開します。

数字

キャッシュを捨てて10秒待ってから1回目(cold)、続けて2回目(hot)。相手は osmium 1.19.1 + ripgrep 15.2.0。

1回目 2回目 3回目 件数
osmium cat … -f osm \| rg 148.17秒(cold) 156.39秒(hot) — 94,979
uvp japan-latest.osm.pbf 東京 29.32秒(.uwvz 作成込み) 6.79秒(再利用・cold) 6.47秒(hot) 94,979
倍率 5.05倍 21.82倍 24.17倍 一致

※ファイル容量の表記について:pbf は 2.29GiB(バイト÷1024³)で、macOS の容量表示(SI・÷1000³)では 2.46GB です。中身を XML にすると、過去記事で「48GB」「51.25GB」と書いている japan-latest.osm(51,254,526,392バイト)と同じ内容になります。

94,979件は、XML 51.25GB を探したときの件数と一致します。入力の形が変わっても答えが変わらないことを、ここで確かめています。

相手は、2回目のほうが遅い

表の2行目に注目してください。osmium cat | rg は cold 148.17秒 → hot 156.39秒で、温まったほうが遅くなっています。

ディスクの読み込みが律速なら、2回目はキャッシュが効いて速くなるはずです。そうならないのは、時間のほとんどが展開と XML の組み立てに使われているからです。2.29GiB を読む時間は、51GB 分の XML を作る時間に比べれば小さい。読み直しの節約が効く場所が、そもそも無いわけです。

同じことは、First Light の第8回で gz を調べたときにも出ました。展開が律速のとき、cold と hot はほとんど同じになります。ここではわずかに逆転していますが、これは測定の揺れの範囲と見ています。

1回で済ませるなら、どこを見るか

uvp の1回目(29.32秒)には .uwvz を作る時間が入っています。1回しか探さないなら、比べるのはこの29.32秒と相手の148.17秒です。それでも5.05倍。

2回目以降が21.82倍・24.17倍になるのは、.uwvz(uvp 用の圧縮形式+索引)を読んでいるからで、読む量そのものが違います。

この値は、9月28日に別の版(1.7.3.5)で測った①27.34秒・②6.43秒・③5.01秒とも大きくずれていません。

「1バイトも違わない XML」を出す、と決めた

pbf から XML を作るとき、形には幅があります。属性の順番、空白の入れ方、どこで改行するか——どれも「だいたい同じ」で済ませられます。

済ませませんでした。出す XML は、osmium の XML 出力と1バイトも同じ(generator 属性だけが uvp)にしています。

理由は、これまでの実測と記事が全部その形に対するものだからです。51.25GB の XML を探して 94,979件。pbf から探しても 94,979件。この2つを並べて「一致した」と言うには、途中の形まで同じでなければ意味がありません。「だいたい同じ」では、件数が合ったときに運なのか正しいのか分かりません。

確かめ方は単純で、osmium が出した XML と cmp で突き合わせるだけです。小さな実物(東京・127 node)はテストに入れて毎回照合し、6.94GB になる関東の pbf でも1バイトも違わないことを確かめています。

protobuf を自前で読んだ理由

pbf を読む部分は、外部のライブラリを足さずに自分で書きました。

protobuf のライブラリを入れれば速く済みます。それでもやらなかったのは、2つあります。

使う型が、数えるほどしかない。 OSM の pbf は仕様が小さく、読む必要があるメッセージは限られています。汎用の実装を抱え込む必要がありません。

読めない入力の断り方を、自分で決めたかった。 zlib 以外の blob、途中で切れたファイル、バージョンの違う header——こうしたものに当たったとき、何と言って止まるかは利用者の体験そのものです。ライブラリの例外をそのまま出すのではなく、何が起きたか・どうすればよいかを書いて止めるようにしています。

展開は blob 単位(約8,000要素のかたまり)で並列にしています。pbf の blob はそれぞれ独立に展開できるので、入力が1本でも CPU を使い切れる。1回目の29.32秒は、この並列があっての値です。

無料版が「断る」のも、設計のうち

uvf に pbf を渡すと、こう言って止まります。

uvf: japan-latest.osm.pbf は OSM の pbf です。UwView Pro(uvp)が扱います:
     uvp osm17/japan-latest.osm.pbf 語

終了コードは2です。

ここで黙って走査しないことが大事でした。テキストとして 東京 を探せば、pbf の中は圧縮されているので1件も見つかりません。そして「0件」は、正しい答えの顔をしています。利用者は「このデータには東京が無いのか」と受け取ってしまう。

見分けは名前ではなく、先頭の [長さ4バイト][BlobHeader("OSMHeader")] で行います。.pbf は protobuf 一般の名前でもあるので、拡張子では決められません。しかもこの判定は uvf と uvp で共有しています。無料版と有料版で「これは pbf か」の答えが食い違ったら、案内そのものが信用できなくなるからです。

確かめ方

  • 相手: osmium 1.19.1(osmium cat PBF -f osm)+ ripgrep 15.2.0(rg -n -F)。XML はファイルに出さず、パイプで流しています
  • 版: uvf・uvp とも v1.7.3.6 “Wide Field”
  • データ: japan-latest.osm.pbf 2.29GiB(node 312,270,548/way 44,696,689/relation 226,670)。中身は XML 51.25GB と同じ
  • 測り方: sync → キャッシュ破棄 → 10秒待機 → 1回目 → 続けて2回目。uvp は .uwvz を消してから1回目を取り、2回目の前にもう一度キャッシュを捨てています
  • 機械: Mac(Apple M4・10コア・メモリ32GB)/外付け USB SSD
  • 照合: 件数だけでなく、本文の集合(行番号を落として並べ替えたもの)で突き合わせて一致。一致しなければ倍率は出しません
  • OS をまたいだ秒数の比較はしていません。この記事の数字はすべて Mac のものです

持ち帰れる一般論

(1)展開が律速のとき、キャッシュは効かない。 相手の2回目が1回目より遅かったのは、時間が「読む」ではなく「作る」に使われていたからです。どこが律速かを先に測らないと、キャッシュを足しても何も速くなりません。逆に言えば、律速が展開なら、展開の回数を減らす以外に手はありません。

(2)変換の形は、1バイト単位で決めておく。 「だいたい同じ XML」を出していたら、件数が一致しても、それが正しさの証拠になりません。あとで突き合わせたいなら、突き合わせられる形で出す。出力の形を決めるのは、速さではなく検証可能性の話です。

(3)読めない入力は、黙って0件にしない。 0件は正しい答えの顔をしています。走査して0件を返すより、名指しで断って終了コードを立てるほうが親切です。そして、その判定は無料版と有料版で共有しないと、案内が食い違います。

(4)土俵が1つ少ない、と正直に言う。 この回の5.05倍は「同じことを速くやった」数字ではありません。相手は展開してから探し、こちらは展開せずに探している。比較の意味が違うときは、倍率より先にそれを書くべきです。

正直に書いておくこと

  • この記事に速さの負けはありません。代わりに、無料版では読めないこと・.uwvz が元の約2.3倍(5.34GiB)になること・pbf には戻せないことが代価です
  • 相手は ripgrep 単体ではありません。rg は pbf を読まないので、osmium cat との組み合わせと比べています。同じ土俵の比較ではありません
  • osmium の設定を詰め切れていない可能性は残ります。osmium cat … -f osm の既定のまま測りました
  • 展開してファイルに出してから探す形は測っていません。51GB の XML をディスクに書く形は、空き容量の都合で測っていません
  • 相手の hot が cold より遅かった件(148.17→156.39)は、展開が律速で読み直しの節約が効かないためと見ていますが、切り分けた計測はしていません
  • 1回目の内訳(pbf→.uwvz の変換と検索)を、この実行では分けて測っていません。9月28日の別の計測では変換21.6秒+検索5.61秒でした
  • .uwvz の大きさ(5.34GiB)は pbf より大きくなります。保管の節約にはなりません。速く何度も探すための形です
  • zlib 以外の blob(lzma・lz4・bzip2)には対応していません。当たったら名指しで断ります
  • 私は比較の一方の開発者です。その分は割り引いてお読みください

使っている道具

UwView は、巨大なテキストファイル・巨大ログを調べるための CLI/GUI ツールです。v1.7.3.6 “Wide Field” では、複数のファイルと7つの圧縮形式、zip、そして OpenStreetMap の pbf を、外部のコマンドなしで探せます。

uvp ならこう書く

# pbf を直接探す。1回目に .uwvz を作り、2回目からはそれを読む(UwView Pro)
uvp japan-latest.osm.pbf '東京'

# 大文字小文字を区別しない/正規表現として
uvp japan-latest.osm.pbf 'TOKYO' -i
uvp japan-latest.osm.pbf 'k="name[^"]*"' -E

# 2語で絞る/前後3行つき
uvp japan-latest.osm.pbf k="amenity" v="parking"
uvp japan-latest.osm.pbf k="amenity" -C 3 k="name"

# 捕まえたところを数える
uvp japan-latest.osm.pbf 'k="amenity"' -uniq 'v="([^"]+)"' -sort count -head 20

# 結果を画面で読む(GUI へ渡す)
uvp japan-latest.osm.pbf '東京' -open

# 索引が何を束ねたものか見る(無料)
uvp japan-latest.osm.pbf.uwvz --info

# 中身の XML を書き出す(pbf には戻せません)
uvp japan-latest.osm.pbf.uwvz -extract -out out/

終了コードは grep と同じで、0=あり・1=なし・2=エラーまたは結果が不完全です。無料版の uvf に pbf を渡した場合も2で終わり、uvp を案内します。

無料版は GitHub Releases から。Mac は brew install --cask amru195704/uwview/uwview、Windows は Scoop(scoop bucket add uwview https://github.com/amru195704/scoop-uwview → scoop install uwview)でも入ります。

リンク

  • gz を展開せずに探す(展開律速は cold と hot が同じ): https://uvp.y42u.net/blog/uwview-fl08-gz-without-extract/
  • 外部コマンドを呼ばずに7形式を探す: https://uvp.y42u.net/blog/uwview-wf02-seven-formats-no-external/
  • 束ねた索引を使い回す(1本消えても作り直さない): https://uvp.y42u.net/blog/uwview-wf04-bundle-keep/
  • 複数ファイルを1コマンドで探す: https://uvp.y42u.net/blog/uwview-wf01-multi-file-one-command/
  • .uwvz を作って、中を探して、取り出す: https://uvp.y42u.net/blog/uwvz-compressed-archive-search-extract/
  • 読む回数だけを減らした13回のまとめ: https://uvp.y42u.net/blog/uwview-fl13-first-light-recap/
  • 実測の全データ(ベンチマーク一覧): https://uvp.y42u.net/benchmarks/
  • ソースコード(GitHub): https://github.com/amru195704/UwView

そして、ストレージを専有している巨大ログを圧縮して保管し、さらに高速に検索したいなら UwView Pro をどうぞ。永続索引・圧縮キャッシュ検索・約 1/9 保管で、開き直しも検索も一段速くなります(全 OS 対応・買い切り/月額プランあり)。


開発者より: アプリ・Kindle 本・公開プロジェクトの一覧は GitHub: amru195704 にまとめています。


タイトルとURLをコピーしました