uvp コマンドを付けました — ripgrep と同じ答えを、10GBで7倍速く。3GBも、冷えていれば勝ちます

技術解説

UwView Pro に、ターミナルから呼べる uvp コマンドが付きました。無料版にも uvf が付いています。

GUI のツールに CLI を足すとき、いちばん怖いのは「答えが違う」ことです。速いかどうかより先に、ripgrep と同じ行が出るかを、3GB・10GB・50GB の3ファイルで突き合わせました。不一致はゼロです。そのうえで速さを測ると、10GB から先は圧倒的に uvp、3GB はファイルがキャッシュに載っているかどうかで入れ替わります

順に書きます。

何ができるか

uvp japan.osm '東京'                          # rg -n -F '東京' と同じ行が出る
uvp japan.osm tokyo -i                        # 大文字小文字を無視
uvp japan.osm 'v="(bus_stop|traffic_signals)"' -E   # 正規表現
uvp japan.osm '^ +<' -E -v                    # 当てはまらない行(grep -v と同じ)
uvp japan.osm 'k="amenity"' 'v="parking"'     # 2語で絞り込み(rg | rg と同じ)
uvp japan.osm 'k="amenity"' -uniq 'v="([^"]+)"' -head 20   # 値の頻度・上位20
uvp japan.osm 'k="amenity"' -C 3 'k="name"'   # 前後3行の中に2語目があるもの
uvp japan.osm -seq 'k="amenity",k="name"'     # この順に現れるもの
uvp japan.osm '東京' -out hits.txt.gz         # 結果を圧縮して保存
uvp japan.osm '東京' -open                    # GUI に渡して続きを見る

終了コードは grep と同じ 0=あり、1=なし。それに加えて 2=上限に当たって打ち切ったがあります。if uvp log 'ERROR'; then がそのまま書けて、暴走もしません。

上限は既定では無制限です。-limit N を書いたときだけ N 件で止まり、2 を返します。これはメモリを守るための保険で、どの N 件が残るかは決まりません——先頭 N 件が欲しいときは -head N です(後述)。

uvp uvf の名前だけで打つには、アプリのヘルプにある「コマンドライン設定…」で登録します。

答えは同じか

ファイル 検索語 rg uvp 一致
3GB 東京 11,274件 11,274件
10GB 東京 11,393件 11,393件
50GB 東京 94,979件 94,979件

件数だけでなく、出力の行を1行ずつ比べています。大小無視・正規表現・除外・2語絞り込み・頻度集計を含めて、不一致はゼロでした。もっと広く突き合わせた記録は 81項目の突き合わせログ にあります。

速さ — キャッシュが冷えているか、温まっているか

同じ秒数でも、条件を書かないと意味がありません。ここでは2つの状態を分けて測っています。

  • コールド: sudo purge の直後。ファイルが OS のキャッシュに載っていない状態。しばらくぶりにそのファイルを触るときがこれです
  • ホット: その直後にもう一度。ファイルがメモリに載っている状態。同じファイルに続けて聞くときがこれです

計測は Mac(MacBook Air / Apple M4 / メモリ32GB / 外付け USB SSD)、ripgrep 15.2.0、uvp 1.6.3、対象は OpenStreetMap 日本データ(XML)です。Windows・Linux(非力なノートPC・VMware 上)では傾向が変わる可能性があります。そちらは別途計測して記事にします。

固定文字列「東京」の検索(.uwvz は作成済み)

サイズ rg コールド/ホット uvp コールド/ホット rg÷uvp コールド/ホット
3GB 3.26秒/0.33秒 1.25秒/1.01秒 2.6倍/1/3.1
10GB 10.96秒/10.89秒 2.37秒/1.55秒 4.6倍/7.0倍
50GB 54.76秒/55.15秒 7.76秒/6.34秒 7.1倍/8.7倍

10GB 以上は、状態によらず uvp です。 rg はコールドもホットも同じ秒数——10GB・50GB はメモリに収まらないので、何度読んでもディスクから読み直すことになります。uvp が読むのは .uwvz(元の 1/9 の大きさ)だけなので、そこで差が付きます。

3GB だけが入れ替わります。 3GB は 32GB のメモリに収まるので、一度読めば全体がキャッシュに載り、その上を素直に走る rg が 0.33秒。uvp は本体を起動してから索引を読む固定費があり、1.01秒。ここは rg です。

3GBで負けたと思っていたら、勝っていた(ただし冷えていれば)

以前この記事には「3GB では負けます」と書いていました。コールドで測り直したら、逆でした。

3GB・コールド rg uvp rg÷uvp 件数
検索「東京」 3.26秒 1.25秒 2.6倍 11,274
大小無視 -i 3.30秒 1.58秒 2.1倍 940,482
正規表現 -E 3.32秒 1.70秒 2.0倍 928,301
正規表現(行頭つき) 3.24秒 1.47秒 2.2倍 222,072
正規表現+大小無視 3.29秒 1.69秒 1.9倍 491,643
除外 -E -v 3.57秒 1.85秒 1.9倍 1
除外 -v(素の語) 3.51秒 3.96秒 1/1.1 7,698,790

7つのうち6つで uvp が速く、だいたい2倍です。理由は 10GB・50GB と同じで、読むバイト数が 1/9 で済むからです。3GB のときだけこれが見えなくなっていたのは、測っていたのが全部ホットだったからでした。

ホットで測ると、この表はほぼ裏返ります(rg 0.33〜0.65秒 対 uvp 1.01〜1.40秒)。ただし -E -v(除外)だけはホットでも uvp が速い(1.74秒 対 3.13秒)——除外は全行を相手にするので、rg にキャッシュの利が効きにくい操作です。

どちらを使うかは、自分の状況で決まります。

  • 同じファイルに続けて何度も聞く(=ホット)→ 3GB なら rg
  • しばらくぶりに開く、ほかの作業をはさむ、複数のファイルを行き来する(=コールド)→ 3GB でも uvp

何問目で元が取れるか

uvp は初回に索引(.uwvz)を作ります。それを含めないと不公平なので、.uwvz を消した状態から積み上げました(1問目はコールド、2問目以降はホット)。

10GB 1問 2問 3問 4問 5問
rg 10.96秒 21.85秒 32.74秒 43.63秒 54.52秒
uvp 14.84秒 16.39秒 17.94秒 19.49秒 21.04秒
50GB 1問 2問 3問 4問 5問
rg 54.76秒 109.91秒 165.06秒 220.21秒 275.36秒
uvp 74.58秒 80.92秒 87.26秒 93.60秒 99.94秒

1問目は必ず負けます(索引を作っているので当然です)。2問目で逆転し、5問聞くと rg の 1/2.6〜1/2.8 になります。以前 258GB で測ったときも 分岐点は1.29問目でした。サイズが変わっても形は同じです。

索引を作る時間そのものは、3GB 4.64秒・10GB 14.84秒・50GB 74.58秒(いずれも1問目の検索を含む合計)です。

無料版の uvf — v1.6.3 で ripgrep 並みになりました

uvf も同じ答えを返します。速さも v1.6.3 で作り直しました。索引を作らず、ファイルを1回通しで読むだけ(行番号・一致・本文を同時に取る)にしたので、媒体の帯域がそのまま速度になります。メモリは 50MB 程度です。

rg コールド/ホット uvf コールド/ホット
3GB 3.26秒/0.33秒 3.32秒/0.55秒
10GB 10.96秒/10.89秒 10.48秒/10.25秒
50GB 54.76秒/55.15秒 50.82秒/50.63秒

以前は 50GB で 205秒(rg の 3.7倍遅い)でした。いまは rg とほぼ同じか、わずかに速い。ただし索引を持たないので、何度聞いても毎回この秒数です——2問目以降の速さが欲しいなら uvp です。

uvf の値打ちは、速度に加えて終了コードと -open です。スクリプトで拾って、当たったら GUI で開く。その受け渡しのためにあります。

先頭 N 件が欲しいとき — -head N

「先頭 N 件だけ見たい」は -head N です。-limit N ではありません。

意味 50GB・「東京」
-head N 1スレッドで先頭から読み、N 件見つけた時点で止まるrg \| head -N とバイト一致 -head 10 0.32秒
-limit N 保持する最大件数。メモリを守る保険。exit 2。どの N 件かは決まらない -limit 1000 0.43秒
既定 無制限 全件 29,114,490件 27.12秒(rg 60.35秒)
uvp japan.osm '東京' -head 10        # 先頭10件。rg | head -10 と同じ結果
uvp japan.osm '東京' -limit 1000     # 1000件たまったら止める(保険・exit 2)

-head 10 の 0.32秒のうち、検索そのものは 0.03秒です。残りの約0.25秒は .NET の起動で、uvp --version だけでも 0.24秒かかります。rg | head の 0.05秒には届きません——先頭 N 件だけを、何度も、サブ秒でという用途は rg の領分です。

ほかに分かったこと

  • 索引を作る時間は、GUI で「開く」時間と同じでした。50GB で CLI 59.6秒、GUI 59.78秒。同じものを作っています
  • .uwvz はもとのファイルより小さい。3GB→0.25GB、10GB→1.16GB、50GB→5.74GB(1/9〜1/12)。しかも uvp japan.osm.uwvz '東京' のように、元ファイルが無くても .uwvz だけで検索できます
  • -extract.uwvz から元ファイルをバイト一致で戻せます。これは無料機能です
  • 外付け SSD では rg 自身も ±20〜40% ぶれます。表の数字は1回の実測で、その幅を含みます

使い分け

  • 3GB 前後で、同じファイルに続けて聞く(温まっている) → rg
  • 3GB 前後でも、しばらくぶりに開く(冷えている) → uvp。2倍速い
  • 10GB を超える → uvp。状態によらず 4.6〜8.7倍
  • 1回だけ聞いて終わり → rg。索引を作る意味がありません
  • 先頭 N 件だけをサブ秒でrg | head
  • スクリプトから呼んで、当たったら人が見る → uvf / uvp の -open

使っている道具

uvpUwView Pro に付属します。内部で UwView Pro 本体を起動する作りなので、ライセンスは Pro のものがそのまま適用されます(Windows・macOS・Linux、1ライセンスで全OS。買い切り/月額。14日間の無料試用あり——試用中も uvp は使えます)。uvf は無料版 UwView に付属します。

数値はすべて Mac(Apple M4 / メモリ32GB / 外付け USB SSD)での自社実測で、独立ベンチマークではありません。Windows・Linux は機器も保存先も違うため、同じ秒数にはなりません。ripgrep は多数ファイルの横断検索では最速の部類で、この記事はそこを争っていません。巨大な単一ファイルに、何度も聞く場面だけの話です。

リンク

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