結論
巨大ファイル用のビューアの速さは、読み出しの速さではなく「ファイルを何回読むか」でほぼ決まります。
51.25GB の1本のテキストファイル(OpenStreetMap 日本の XML)を、外付け USB SSD(素読み約 950 MB/s)から開いて、1語検索して、行番号つきの結果一覧が画面に出るまでを測りました。
| 50GB・「開いて、探して、一覧が出る」まで | 秒 | 読み出し換算 | ファイルを読んだ回数 |
|---|---|---|---|
| 自作ビューア・1つ前の版 | 189.5秒 | 約 270 MB/s 相当 | 2回(しかも半速) |
| klogg 24.11 | 108.1秒 | 約 475 MB/s 相当 | 2回 |
| 自作ビューア・現行版 | 53.7秒 | 910 MB/s | 1回 |
素読みが 950 MB/s のディスクなら、51GB を1回読むのに約 54秒かかります。53.7秒は、その「1回」そのものです。
klogg は開くときに1回、探すときにもう1回読むので約2倍。1つ前の自作版は2回読んだうえに1回あたりの読み方が半速だったので、3.5倍でした。
この記事は「どうやって2回を1回にしたか」と、「その差を測るときに踏んだ落とし穴」の話です。
測った条件
- ファイル: 51,254,526,392 バイト(3GB・10GB はこれを1億行ずつ切った分割片)。258GB(アメリカ全土・45億行)でも一部確認
- 語:
東京(固定文字列)。ヒット件数は 3GB 11,274/10GB 11,393/50GB 94,979 で、比較した両ツールで一致 - 毎回
sudo purgeして 10秒待ってから測る(コールド)。2回目の値は「hot」と明記 - Mac M4/メモリ 32GB/外付け USB SSD。klogg 24.11.0.1685。自作側は UwView 無料版 v1.6.5(1つ前)と v1.6.6(現行)
- GUI はストップウォッチで手計測。「開く」=索引が終わって全体をスクロールできるまで、「検索」=開き終わってから語を入れて件数が確定するまで、「通し」=開く操作から行番号つきの結果一覧が出るまで
- MB/s = バイト ÷ 1024³ ÷ 秒
なぜ GUI ビューアは2回読むのか
巨大ファイルを「開く」とは、実際には行の切れ目を数えて索引を作ることです。行番号を出すにも、スクロールバーの位置を行に対応させるにも、先頭から改行を数えないと分かりません。だから開くときに1回、全体を読みます。
そのあと「検索」すると、今度は語を探すために先頭からもう1回読みます。開くときに作った索引は行の位置しか持っていないので、検索には役に立たない。これが「2回読み」の正体で、klogg も、1つ前の自作版も、この構造でした。
| 開く(1回目の読み) | 検索(2回目の読み) | 合計 | |
|---|---|---|---|
| klogg | 52.55秒(930 MB/s) | 55.59秒(879 MB/s) | 108.14秒 |
| 自作・1つ前 | 100.6秒(486 MB/s) | 88.9秒(550 MB/s) | 189.5秒 |
klogg の数字を読み出し速度に直すと 930/879 MB/s。このディスクの素読みを、ほぼ使い切っています。 つまり klogg は「遅い」のではなく、2回読む設計の上限まで来ている。ここを抜くには、読み方を速くしても無理で、回数を減らすしかない、というのがこの表から出る答えです。
一方の自作版は 486 MB/s。同じディスクで半分しか出ていない。これは索引作りの実装が1パスあたり2回バッファを舐めていたためで、設計ではなく実装の問題でした。同じ機械で、CLI 版の同じ検索は 962 MB/s 出ていたので、GUI 側が遅い理由がディスクにないことは最初から分かっていました。
「1回」にする
やったことは2つです。
1. 索引の完成を待たずに検索を始める。 開く操作の直後、索引がまだ 10% しかできていなくても検索語を受け付ける。検索スレッドは索引スレッドと同じ順でファイルを前から読むので、OS のページキャッシュ上で2本のスレッドが同じブロックを追いかける形になり、ディスクからの読み出しは実質1回で済みます。
2. 索引作りを CLI と同じ読み方にする。 CLI 版(uvf)は前の版で「大きなブロックで順次読み、改行と検索語を同じループで数える」形に作り直してあり、それが 962 MB/s の根拠でした。GUI の索引ビルダーをその読み方に寄せました。
結果です。
| 50GB | 1つ前 | 現行 | klogg |
|---|---|---|---|
| 開くだけ | 100.6秒(486 MB/s) | 50.44秒(969 MB/s) | 52.55秒(930 MB/s) |
| 開いたあとの検索 | 88.9秒(550 MB/s) | 53.50秒(914 MB/s) | 55.59秒(879 MB/s) |
| 開きながら探す(通し) | 189.5秒 | 53.7秒(910 MB/s) | 108.14秒 |
「開くだけ」50.44秒と「通し」53.7秒の差は 3.3秒。検索が開くのと同時に走っているので、ファイルを読み終わったあとに残るのは検索の尻尾だけです。
3GB・10GB でも同じでした。
| 開くだけ | 3GB | 10GB | 50GB |
|---|---|---|---|
| klogg | 3.65秒 | 10.98秒 | 52.55秒 |
| 自作・現行 | 2.99秒 | 10.13秒 | 50.44秒 |
| 読み出し(自作) | 967 MB/s | 966 MB/s | 969 MB/s |
3サイズとも同じ 967〜969 MB/s に揃ったのが、いちばん安心した数字です。サイズによらず一定=媒体の速度そのもので、これ以上はディスクを替えないと縮みません。
踏んだ落とし穴
「開く」+「検索」を足して「通し」にしてはいけない。 1回読みにすると、開くのと探すのが同時に走るので、50.44+53.50=103.9秒ではなく 53.7秒になります。逆に klogg のような2回読みの設計では足した値がほぼ通しに一致する(52.55+55.59=108.14)。設計によって足し算が成立したりしなかったりするので、通しは通しで測る必要があります。
3GB では2回読みの不利が消える。 3GB はメモリ(32GB)に載るので、2回目の読みがページキャッシュから返ります。klogg の 3GB 検索は 0.56秒、自作も 0.32秒。この大きさで「1回読み」の効果を示そうとしても差は出ません。差が出るのはメモリに載らないサイズからで、10GB を境に klogg 比 1.08倍(開くだけ)→ 2.18倍(通し)に開きます。逆に言えば、3GB のベンチだけ見せて「2倍速い」と書いたら嘘になります。
手計測の下限。 3GB の通しは測れませんでした。検索語を打ち込んでいる間に開き終わるからです。これは失敗ではなく「この大きさでは待ち時間が無い」という結果なのですが、表には「測定せず」としか書けません。10GB の通しも、開くのが 20% 進んだところで検索を開始する操作が手でばらつくので、秒数として公表していません。
キャッシュの取りこぼし。 258GB で uvf … -open を測ったとき、最初の値が 250.68秒(984 MB/s)と、素読みの 950 MB/s を上回りました。直前に途中で止めた実行のキャッシュが数%効いたと判断して purge からやり直し、265.21秒(930 MB/s)を採用しています。素読みを超える値が出たら、速くなったのではなくキャッシュを疑う、というのが一番役に立ったルールです。
258GB でも同じか
258.68GB・45億行の1本でも確かめました。
| 258GB | 秒 | 読み出し |
|---|---|---|
| klogg が開き終わるまで | 258秒 | 956 MB/s |
| 自作・現行が開き終わるまで | 255.5秒 | 966 MB/s |
自作・現行の検索(New York) |
256.5秒 | 962 MB/s |
| 自作・1つ前が開き終わるまで | 530.1秒 | 465 MB/s |
50GB の 5倍のサイズで、読み出し速度は同じ帯に収まりました。サイズを5倍にしても MB/s が動かないことが、実装が媒体律速になっている証拠になります。
一般化できること
- GUI ビューアの「開く」と「検索」は、別々に読むのが普通。両方を測って読み出し速度に直せば、そのツールがファイルを何回読んでいるかが分かる(合計 MB/s ÷ 素読み MB/s ≒ 回数)
- 素読み速度を出し切っているツールに勝つには、読む回数を減らす以外に手がない。klogg は 2回読みの上限で、そこに1回読みで並んだだけ、というのがこの記事の全部
- 足し算が通しと一致するかどうかは設計で変わる。通しは通しで測る
- メモリに載るサイズでは何も分からない。差はメモリを超えたところで初めて出る
- 素読みを超えたら疑う。キャッシュを捨ててから測り直す
付記
題材にした自作ビューアは UwView(無料版)で、上の値は v1.6.6 のものです。klogg は無料でオープンソースの優れたツールで、1つ前の版まではこちらが負けていました。条件と全データはこちらにまとめてあります。