したいこと
- 100万行程度のテキストファイルから、ユニークな ID でレコードを抽出したい
- 上記処理を数万~数億回と行う
- できるだけ早いほうがいい
対象のファイル
$ ll -h list.csv
-rw-rw-r-- 1 togawa togawa 37M 8月 22 16:51 2017 list.csv
$ wc -l list.csv
995283 list.csv
$ cat list.csv
ASDFGHJ,0,0,0000,0,0,1,0,0,0,東京都三鷹市
...
ZXCVBNM,1,1,1234,0,1,25,0,0,0,東京都八王子市
ポイント
- 大文字小文字は区別させない
- パイプを介さない
- 件数を1件にする
大文字小文字は区別させない
-i
オプションをつけると大文字と小文字の区別なく検索してくれるが、その分処理が遅くなる。
$ time cat list.csv | grep ZXCVBNM
ZXCVBNM,1,1,1234,0,1,25,0,0,0,東京都八王子市
real 0m0.048s
user 0m0.030s
sys 0m0.018s
$ time cat list.csv | grep -i ZXCVBNM
ZXCVBNM,1,1,1234,0,1,25,0,0,0,東京都八王子市
real 0m1.845s
user 0m1.816s
sys 0m0.022s
できれば前処理でこのオプションを付けなくても済むようにしておきたい。
なお、 grep -i
については grep 2.17 より大幅な高速化がなされている。
http://news.mynavi.jp/news/2014/02/21/053/
今回検証した環境ではなんと 2.6.3 だった。そりゃ遅いわけである。
パイプを介さない
ファイルを読み込んで処理するときは cat
してからパイプで渡すという私の癖がある。パイプで渡された方のコマンドは引数が減る分、すっきりして読みやすくなる (と感じている)。
しかし、その分余計な fork が発生してしまうので処理は遅くなる。
$ time cat list.csv | grep ZXCVBNM
ZXCVBNM,1,1,1234,0,1,25,0,0,0,東京都八王子市
real 0m0.048s
user 0m0.030s
sys 0m0.018s
$ time grep ZXCVBNM list.csv
ZXCVBNM,1,1,1234,0,1,25,0,0,0,東京都八王子市
real 0m0.037s
user 0m0.032s
sys 0m0.005s
件数を1件にする
grep の -m
オプションは 何件までヒットさせてもよいか を指定するものである。言い換えると、 この件数を超えたらその時点で検索処理をやめる とすることができる。
今回のようにユニークなキーで grep する場合は検索結果が 1 件であることが保証されているため grep -m 1
としてもよさそうである。
$ time grep -m 1 ASDFGHJ list.csv
ASDFGHJ,0,0,0000,0,0,1,0,0,0,東京都三鷹市
real 0m0.001s
user 0m0.000s
sys 0m0.000s
$ time grep ASDFGHJ list.csv
ASDFGHJ,0,0,0000,0,0,1,0,0,0,東京都三鷹市
real 0m0.035s
user 0m0.028s
sys 0m0.007s
上記の例は、 ASDFGHJ
というキーを持つレコード (ファイルの先頭行) を grep したときの結果である。 -m -1
した場合、1レコード目の検索で早速ヒットしたため、以降の検索は中止している。
一方、 -m 1
していない方はヒットしようがしまいが最終レコードまで検索は続けるため、その分処理時間は大きくなる。
まとめ
- grep 処理をループさせる場合、書き方によっては処理のボトルネックになりかねない
- grep によって実現させたいことを明確化し、適切なオプションを使うことである程度の処理速度改善が見込める
- 特にユニークなキーで検索する場合は
-m 1
が有効
- 特にユニークなキーで検索する場合は