Grep -E,Sed -E-使用'[x] {1,9999}'时性能低下,但是为什么呢?


9

当此选项使用grep或且模式是所使用的regexp的一部分时,这些命令的性能会降低。为了更加清楚,下面应用了一些测试。[1] [2]sed--extended-regexp{1,9999}

  • 的相对性能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

>真实的21m43.947s

测试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/nulltime grep -E '[0-9]{1,9999}' </dev/null。即使没有输入,第二个命令仍然很慢(在16.04上)。正如预期的那样,省略-E和逸出{}行为相同和更换-E-P不慢(PCRE是一个不同的发动机)。最有趣的是,速度 [0-9].x甚至还要多少[0123456789]。对于任何那些和{1,9999}grep消耗巨大的内存大小; 我还不敢让它运行超过10分钟。
伊莱亚·卡根

3
@αғsнιη不,{ }' '引用 ; 贝壳将它们原封不动地传给了grep。无论如何,{1,9999}这将是一个非常快速和简单的支撑扩展。外壳程序会将其扩展为1 9999
伊莱亚·卡根

4
@αғsнιη我不太清楚您的意思,但这绝对与外壳无关。在长时间运行的命令中,我使用pstop验证是否grep传递了预期的参数,并且该参数(而不是bash)消耗了大量的RAM和CPU。我期望grep并且sed都使用libc中实现的POSIX regex函数进行BRE / ERE匹配;除非开发人员选择使用该库,否则我真的不应该专门讨论设计。grepgrep
伊莱亚·卡根

3
我建议您将测试替换为time grep ... < /dev/null,以免人们将实际问题与馈入数据grep以及其他无关紧要的东西混为一谈。
muru 17/09/29

Answers:


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编译grepCPPFLAGS=-DDEBUG ./configure && make,并运行这些命令,你会看到在行动的指数效应。深入了解这意味着要通过DFA的大量理论,并深入研究gnulib regexp实现。

在这里,您可以使用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

3
@SergiyKolodyazhnyy,您可以使用grep -Po '[0-9]{1,9999}似乎没有问题的方法。
斯特凡Chazelas

1
它不仅在sed -Egrep -E,但awk也有这种低性能(约最后awk命令)。也许awk还不能使用PCRE?
αғsнιη
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.