GNU grep 2.20 リリース

6/3にGNU grep 2.20がリリースされました。

GNU grep 2.20 は、GNU grep 2.19 で行われた多くのパフォーマンス改善に対するメンテナンス・リリースです。結果不正の不具合が見つかったため、GNU grep 2.19のリリースから、たった11日後にリリースされる運びとなりました。

それでは、GNU grep 2.20 で修正された主な不具合の内容について、例をあげながら見て行くことにしましょう。

bug#17617 - bug / regression in grep-2.19

GNU grep 2.19 において、メモリ割り当ての最適化が行われましたが、その際に混入してしまった不具合です。

下記のケースでは、どちらも行を出力すべきではありませんが、GNU grep 2.19 では、どちらも「aa」を出力してしまいます。*1

$ echo aa | grep 'a\(b$\|c$\)'
aa
$ echo aa | grep '\(b$\|c$\)a'
aa

bug#17640 - grep with -m reads the entire input

-m オプションを指定されると、 個のマッチを見つけた時点で処理を打ち切るのが仕様上の動作です。

下記のケースでは、「x」 を出力して直ちに正常終了すべきですが、GNU grep 2.19 では、「x」を出力した後に直ちに終了せず、入力の末尾まで読み込もうとしてしまいます。その結果、Ctrl+Cなどで処理を停止させない限りプロンプトが返りません。

$ yes x | grep -m1 x

*1:GNU grep 2.18 以前のリリースには、この不具合はありません。

GNU grep 2.19 リリース

5/23にGNU grep 2.19 がリリースされました。

GNU grep 2.19 の最大の目玉はパフォーマンスの改善です。今回のリリースはメディアには取り上げられませんでしたが、メディアに取り上げられたGNU grep 2.17 よりも、はるかに多くのパフォーマンスの改善がなされています。

特に注目すべき点は、マルチバイト・ロケールに対してはもちろん、シングルバイト・ロケールに対してもパフォーマンスの改善がなされていることです。少なくとも私の知る限りでは、GNUgrep 2.6以降でシングルバイト・ロケールに対するパフォーマンスの改善がなされたことありません。試しにシングルバイト・ロケールにおいて、Wikipedia 日本語版のデータベースを「Wikipedia」 で検索した結果、GNU grep 2.19 は従来のリリースよりも約10% 速く結果を返しました。また、Oracle Database をインストールしたディレクトリを、「Oracle」で検索した結果も同様でした。

それでは、主な改善点について、例をあげながら見て行くことにしましょう。

Boyer-Moore法 におけるワースト・ケースの改善

GNU grepは固定文字列の検索に対してBoyer-Moore 法を使用しますが、従来のリリースにおいては、ワースト・ケースに対するパフォーマンスがよくありませんでした。GNU grep 2.19では、Boyer-Moore 法の2 つのワースト・ケースに対してパフォーマンスの改善がなされています。

$ yes jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj | head -10000000 >k
$ env LC_ALL=C time -p grep kjjjjjjjjjjjjjjjjjjj k

  grep-2.18: real 4.12    user 4.06    sys 0.05
  grep-2.19: real 0.46    user 0.40    sys 0.05  <<★9倍高速化

$ env LC_ALL=C time -p grep jk k

  grep-2.18: real 1.45    user 1.41    sys 0.04
  grep-2.19: real 0.09    user 0.02    sys 0.06  <<★16倍高速化

DFA スーパーセットの利用

DFA スーパーセットとは、GNU grepが苦手とする後方参照 (「\1、「\2」など) や、マルチバイト・ロケールにおけるワイルドカード (「.」や [a-z] など) を省いた別の正規表現です。事前にDFA スーパーセットを使用して、マッチし得ない行をフィルタすることで、GNU grepが苦手としていたパターンに対するパフォーマンスが大きく改善されています。

$ yes abcdabc | head -50000000 >l
$ env LC_ALL=C time -p grep '\(a\)bcd\1bd' l

  grep-2.18: ... slow extreamly ...
  grep-2.19: real 1.48   user 1.40     sys 0.07  <<★超高速化
  
$ env LC_ALL=ja_JP.eucjp time -p grep 'abcd.bd' l

  grep-2.18: ... slow extreamly ...
  grep-2.19: real 1.45    user 1.38    sys 0.07  <<★超高速化

$ env LC_ALL=ja_JP.eucjp time -p grep -i k k

  grep-2.18: real 13.27   user 13.17   sys 0.08
  grep-2.19: real 1.40    user 1.33    sys 0.06  <<★9倍高速化

Boyer-Moore 法の-iオプションが利用されたケースへの適用

従来のリリースでは、-iオプションが利用された場合、[Tt][Ee][Ss][Tt]のような、同等の正規表現に置き換えて処理していました。GNU grep 2.19 では、シングルバイト・ロケールにおいて、ワースト・ケースが改善されより高速になったBoyer-Moore 法を使用します。*1

$ yes jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj | head -10000000 >k
$ env LC_ALL=C time -p grep -i abc k

  grep-2.18: real 1.07    user 1.01    sys 0.05
  grep-2.19: real 0.15    user 0.08    sys 0.06  <<★7倍高速化

行頭または行末指定を含む固定文字列検索の改善

従来のリリースでは、行頭「^」または行末「$」指定を含む固定文字列検索に対しては、Boyer-Moore 法とDFA*2の合わせ技で検索していました。GNU grep 2.19 では、このようなケースに対しても、Boyer-Moore 法のみで検索するようになりました。

$ yes jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjk | head -10000000 >m
$ env LC_ALL=C time -p grep -i j$ m

  grep-2.18: real 2.12    user 2.07    sys 0.05
  grep-2.19: real 0.53    user 0.47    sys 0.05  <<★4倍高速化

まとめ

GNU grep 2.19 は、少なくとも私の知るGNU grep 2.6以降で最もパフォーマンスの改善がなされたリリースです。マルチバイト・ロケールに対してはもちろん、シングルバイト・ロケールに対しても大きな改善がなされています。

おことわり

性能検証は、Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz RHEL 6.5 GCC 4.4.7上で実施しています。カーネルやOSライブラリのバージョンや、マシン性能によって性能が大きく異なることがありますのであらかじめご了承ください。

*1:マルチバイト・ロケールにおいては、従来通り同等の正規表現に置き換えて処理されます。ただし、GNU grep 2.19 は、DFA スーパーセットを利用するため、ほとんどのケースでは従来のリリースよりはるかに高速です。

*2:DFA は、Boyer-Moore 法では処理できないやや複雑なパターンを処理します。