正则表达式的交替在大文件中似乎非常慢

Alternation in regexes seems to be terribly slow in big files

我正在尝试使用这个正则表达式:

my @vulnerabilities = ($g ~~ m:g/\s+("Low"||"Medium"||"High")\s+/);

files such as this one 的块上,从一个 "sorted" 到下一个的块。每个都必须是几百千字节,所有这些加起来总共需要 1 到 3 秒(每次迭代除以 32)。

如何加快速度?

这可以稍微简化一下。你前后都用\s+,但是有这个必要吗?我认为你只需要确保单词边界或一个空格,因此,你可以使用

\s("Low"||"Medium"||"High")\s

或者您可以使用 \b 而不是 \s

第二步不使用捕获组,而是使用非捕获组,因为正则表达式引擎会浪费时间和内存用于 "remembering" 组,所以你可以尝试:

\s(?:"Low"||"Medium"||"High")\s

TL;DR 我已经使用您的示例数据比较了最近 rakudo 上的解决方案。我在这里展示的丑陋的蛮力解决方案的速度大约是 Liz 展示的令人愉快的优雅解决方案的两倍。通过分解数据并并行处理它,您可能可以将时间提高另一个数量级或更多。如果这还不够,我还会讨论其他选项。

交替似乎是一个转移话题

当我消除交替(只留下"Low")和运行最近的rakudo上的代码时,所花费的时间大致相同。所以我认为这是一个转移注意力的问题,并没有进一步研究这方面。

并行处理看起来很有希望

从您的数据中可以清楚地看出,您可以将其分解,在任意行处拆分,然后并行地对每个部分进行模式匹配,然后组合结果。

根据与您的系统和处理的数据相关的各种因素,这可能会给您带来实质性的胜利。

但我还没有探索过这个选项。

我见过最快的结果

我见过最快的结果是使用这段代码:

my %counts;
$g ~~ m:g / "\t " [ 'Low' || 'Medium' || 'High' ] \n { %counts{$/}++ } /;
say %counts.map: { .key.trim, .value }

这显示:

((Low 2877) (Medium 54))

此方法包含与 Michał Turczyn 讨论的那些类似的更改,但更加努力:

  • 我已经扔掉了所有捕获,不仅懒得捕获'Low'什么的,还扔掉了全部场比赛结果。

  • 我用具体的字符而不是字符 类 替换了 \s+ 模式。我这样做是基于我对最近的 rakudo 的随意测试表明速度更快。

超越 raku 的正则表达式

Raku 专为完整的 Unicode 通用性而设计。而且它的正则表达式引擎非常强大。但看起来您的数据只是 ASCII,而您的模式是一个典型的非常简单的正则表达式。因此,您正在使用大锤敲碎坚果。这不应该真的很重要——大锤应该也像胡桃夹子一样好——但 raku 的正则表达式引擎到目前为止优化仍然很差。

也许这个坚果只是一个简单的例子,您只是想知道如何将 raku 的内置正则表达式功能推向最大当前性能。

但如果不是,并且您需要更快的速度,并且 raku 中的这个或其他更好的解决方案的加速,加上并行处理,不足以让您到达需要去的地方,值得考虑不使用 raku 或将其与其他工具一起使用。

将 raku 与其他工具一起使用的一种惯用方法是使用 Inline,在本例中最明显的方法是 Inline::Perl5。使用它,您可以尝试 perl 的快速默认内置正则表达式引擎,甚至可以使用其正则表达式插件功能插入一个非常快速的正则表达式引擎。

并且,考虑到您要匹配的模式的简单性,您甚至可以通过快速编写一些低级原始文本搜索工具的胶水来完全避开正则表达式(可能保存字符偏移量,然后生成相应的 raku匹配结果中的对象)。

示例文件的检查显示字符串仅作为整行出现,以制表符和 space 开头。从您的回复中,我进一步了解到您真的只对计数感兴趣。如果是这样的话,那么我会建议这样的解决方案:

my %targets = "\t Low", "Low", "\t Medium", "Medium", "\t High", "High";
my %vulnerabilities is Bag = $g.lines.map: {
    %targets{$_} // Empty
}
dd %vulnerabilities;  # ("Low"=>2877,"Medium"=>54).Bag

这在我的机器上运行大约需要 0.25 秒。

深入研究问题域总是值得的!