CLI でのテキスト処理を高速化する
最近、個人的に数百MBから数GBクラスのテキストファイルを扱う機会が増えています。これくらいのサイズだと、手元のマシンだけで十分対応可能な範囲ではあるのですが、扱いを間違えると時間が掛かってつらいことになります。結論から言うと、とにかく LC_ALL=C
を指定しようというのと、OS X であれば初めから入っているコマンドではなく最新の coreutils を使おうという2点なのですが、それだけ終わってしまうとあんまりなので、手元の環境で計測した数字を出しつつ紹介したいと思います。
なお、この記事中の速度計測はクアッドコアの Core i7 (2.2GHz) を搭載した MacBook Pro 15インチ (Mid 2014) で行いました。OS は OS X El Capitan です。テスト用のデータは以下のようなコマンドで生成しました。
LC_ALL=C を指定しよう
きっかけは、sort
が異様に遅いということでした。高々数百MB程度のデータをソートするのに何分も掛かるのはおかしいと思い、調べてみたところ、ロケールの設定が原因との情報にたどり着きました。LC_ALL=C
の環境変数を指定すると、ロケールを考慮した文字列比較を行わずにバイト列としてソートするようになるため、高速化されるようです。
試してみたところ、その差は歴然。ロケールを考慮する必要が無ければ、忘れずに LC_ALL=C
を指定したいですね。
ちなみに、LC_ALL=C
を指定することで comm
, grep
, look
, uniq
なんかも速くなるようです。
coreutils を使おう
こちらのきっかけは、tail -n+2
でファイルの2行目以降を切り出そうとすると cat
でファイル全体を出力するのと比べてとんでもなく遅くなるということでした。
意味がわからないですね。ただ、知人に相談したところどうも Linux だと cat
と遜色ない速度が出るとのこと。そこで、GNU の提供するコマンド群の最新バージョンを利用できる coreutils
を試してみることに。
coreutils
版 tail
である gtail
で試してみると、確かに cat
に近い速度が出るようになりました。BSD の tail
がなぜ遅いのかまでは確認していませんが、とにかく -n
オプションを使ったアクセスのパターンにおいて GNU の tail
は優秀だということがわかりました。
そうなると、他のコマンドはどうなのかも気になるところ。他にもいくつか試してみました。
sort
LC_ALL=C
を付けた sort
よりさらに速くなりました。376% cpu
という結果でわかるように、最新の coreutils
に含まれる sort
はマルチコア CPU を活用してソートしてくれるようです。
ちなみに、El Capitan 付属の sort
も coreutils
のもののようですが、バージョンが 5.93 で、コピーライトの表記は2005年でした。今は 8.24 なのでかなり古いですね。coreutils
のような地味なコマンドも、10年の間に着実に進化し続けているとも言えますね。カッコいい。
wc
実装する立場で考えると sort
ほどは工夫の余地は無さそうな wc
ですが、それでも coreutils
に乗り換えるとかなり速くなります。すごい。
なお、El Capitan 付属の wc
は古い coreutils
のものではなく、BSD 由来のものでした。
エイリアスを設定する
個人的には sort
でロケールを考慮したいことはまずないですし、g のプレフィックスを意識するのも面倒なので、シェルのエイリアスを設定することにしました。$(brew --prefix coreutils)/libexec/gnubin
にパスを通すことで coreutils
のコマンドで sort
以外も含めてごっそり置き換えることも可能ですが、副作用がこわいので、使いたいと思ったものについて手でエイリアスを足す運用にしています。どうせ LC_ALL=C
付きのエイリアスは作るわけですし。
ggrep
は coreutils
のものではなく、以下のようにしてインストールしたものです。