Grep -E、Sed -E-'[x] {1,9999}'を使用するとパフォーマンスが低下しますが、なぜですか?


9

ときgrepsedオプションとともに使用され--extended-regexp、パターンが{1,9999}使用されている正規表現の一部であり、これらのコマンドの性能が低くなります。より明確にするために、以下はいくつかのテストに適用されます。[1] [2]

  • 相対性能grep -Eegrepおよびsed -E、で作られたので、唯一のテストはほぼ等しいgrep -Eが設けられています。

テスト1

$ time grep -E '[0-9]{1,99}' < /dev/null

real    0m0.002s

テスト2

$ time grep -E '[0-9]{1,9999}' < /dev/null

> real    0m0.494s

テスト3

$ time grep -E '[0123456789] {1,9999}' </ dev / null

>リアル21分43.947秒

テスト4

$ time grep -E '[0123456789]+' < /dev/null
$ time grep -E '[0123456789]*' < /dev/null
$ time grep -E '[0123456789]{1,}' < /dev/null
$ time grep -P '[0123456789]{1,9999}' < /dev/null

real    0m0.002s       

このパフォーマンスの大きな違いの理由は何ですか?


3
これは興味深い所見です。パースツリーを構築する方法を正確に見つけるには、grepの内部を深く掘り下げる必要があると思います(比較すると興味深いでしょう[0-9]+
steeldriver

3
入力は関係ありません。@steeldriverが示唆するように、スローダウンマッチングに先行します。より単純なテストはtime grep -E '[0-9]{1,99}' </dev/nullvs time grep -E '[0-9]{1,9999}' </dev/nullです。入力ない場合でも、2番目のコマンドは遅くなります(16.04)。予想されるように、省略-E及びエスケープ{}同様に振る舞い、置換-Eとする-P(PCREが異なるエンジンである)低速ではありません。最も興味深いのはどのくらいです速く [0-9]よりもある.xとさえ[0123456789]。これらのいずれかとして{1,9999}grepRAMの膨大な量を消費し、あえて10分以上実行させませんでした。
Eliah Kagan

3
@αғsнιηいいえ、{ }されて' '引用されました。シェルはそれらを変更せずにに渡しますgrep。とにかく、{1,9999}非常に高速でシンプルなブレース展開になります。シェルはそれをに拡張し1 9999ます。
Eliah Kagan

4
@αғsнιηどういう意味かわかりませんが、これは間違いなくシェルとは関係ありません。長時間実行コマンドの間に、私が使用psしてtop検証するためにgrep期待される引数が渡され、それが、ないことをbash、RAMとCPUの多くを消費します。私は期待してgrepおり、sed両方ともBRE / EREマッチングのためにlibcに実装されたPOSIX regex関数を使用しています。開発者がそのライブラリを使用することを選択した場合を除いて、デザインについて具体的に話す必要はありませんでした。grepgrep
Eliah Kagan

3
テストをtime grep ... < /dev/nullに置き換えることをお勧めします。これにより、実際の問題が、フィードされたデータgrepやその他の無関係なものと混同されなくなります。
muru、2017

回答:


10

時間のかかるのはマッチングではなく、REの構築です。また、RAMもかなり使用していることがわかります。

$ valgrind grep -Eo '[0-9]{1,9999}' < /dev/null
==6518== HEAP SUMMARY:
==6518==     in use at exit: 1,603,530,656 bytes in 60,013 blocks
==6518==   total heap usage: 123,613 allocs, 63,600 frees, 1,612,381,621 bytes allocated
$ valgrind grep -Eo '[0-9]{1,99}' < /dev/null
==6578==     in use at exit: 242,028 bytes in 613 blocks
==6578==   total heap usage: 1,459 allocs, 846 frees, 362,387 bytes allocated
$ valgrind grep -Eo '[0-9]{1,999}' < /dev/null
==6594== HEAP SUMMARY:
==6594==     in use at exit: 16,429,496 bytes in 6,013 blocks
==6594==   total heap usage: 12,586 allocs, 6,573 frees, 17,378,572 bytes allocated

割り当ての数は反復回数にほぼ比例しているようですが、割り当てられたメモリは指数関数的に増加しているようです。

それは、GNU正規表現がどのように実装されるかにかかっています。を使用してGNU grepをコンパイルしCPPFLAGS=-DDEBUG ./configure && make、それらのコマンドを実行すると、指数関数的な効果が見られます。それよりも深く行くことは、DFAに関する多くの理論を経て、gnulib正規表現の実装に飛び込むことを意味します。

ここでは、同じ問題を抱えていないように見えるPCREを代わりに使用できますgrep -Po '[0-9]{1,65535}'(最大[0-9](?:[0-9]{0,10000}){100}は1から1,000,001回の繰り返しで常に実行できます)は、よりも時間もメモリもかかりませんgrep -Po '[0-9]{1,2}'


これを回避する方法はありますか?
Sergiy Kolodyazhnyy 2017

3
@SergiyKolodyazhnyy、grep -Po '[0-9]{1,9999}問題ないと思われるものを使用できます。
ステファンChazelas

1
それはしていないだけですsed -Egrep -E、しかし中にawkも、この低性能の持っている(最後のawkコマンドについては)。多分awkまたPCREを使用することはできませんか?
αғsнιη
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.