Perl6正则子规则/命名正则远慢于显式正则,如何优化提速?
解决Perl 6动态正则/子规则的性能问题
你遇到的性能瓶颈核心原因是:动态正则片段(<{ ... }>)会在每次匹配时重新执行代码并构建正则,百万级的循环会把这个微小的单次开销放大到无法忍受的程度。而显式写死的正则是一次性预编译完成的,所以速度差距巨大。
为什么原来的子规则/动态正则慢?
看你第一个代码里的这段:
my regex a { [ <{<iron copper carbon>.join("||")}> ] }
每次调用<a>(也就是每一行匹配时),Perl 6都会执行<iron copper carbon>.join("||")生成字符串,然后把这个字符串解析成正则片段——这个过程在160万行的循环里重复了160万次,光是字符串拼接和正则解析的开销就足以拖慢整个程序,甚至因为重复分配内存导致停滞。
同样,你尝试的my $a=rx/ [ <{ < iron copper carbon > .join("||") }> ] /也有一样的问题:每次使用$a匹配时,都会重新执行里面的代码生成正则。
解决方案:预编译正则片段
我们只需要把目标词汇的正则提前编译一次,之后在匹配时直接复用这个预编译好的正则对象,就能和显式写的正则达到同样的速度,同时保持代码的抽象性。
改进后的代码示例(保持抽象性)
perl6 -e ' # 用数组维护目标词汇,后续修改只需改这里 my @target-terms = <iron copper carbon>; # 预编译目标词汇的正则,只执行一次编译 my $target-re = rx:i/ @target-terms >> /; # 可选:用命名子规则封装,保持代码可读性 my regex target { <$target-re> }; my $x = 0; for "/tmp/DataRaw".IO.lines { $*ERR.print( "$x 1608240 \r" ); ++$x; .say if m/:i beginSection \s+ <target>/ or (m/:i \s+ <target> \s+ / ff m/:i doneSection/); } '
或者更简洁的版本:
perl6 -e ' my @target-terms = <iron copper carbon>; my $target-re = rx:i/ @target-terms >> /; my $start-re = rx:i/ beginSection \s+ <$target-re> /; my $in-section-re = rx:i/ \s+ <$target-re> \s+ /; my $end-re = rx:i/ doneSection /; my $x = 0; for "/tmp/DataRaw".IO.lines { $*ERR.print( "$x 1608240 \r" ); ++$x; .say if m/$start-re/ or (m/$in-section-re/ ff m/$end-re/); } '
关键改进点说明
- 预编译正则:
rx:i/ @target-terms >> /会把数组@target-terms的元素自动用||连接,并且在初始化时就完成编译,之后每次匹配都直接复用这个编译好的正则对象,没有重复计算的开销。 - 避免
<{ ... }>的即时求值:把动态生成正则的逻辑移到循环之外,只执行一次,而不是每一行都重新生成。 - 保持抽象性:用数组
@target-terms维护目标词汇,后续修改词汇只需要修改数组,不需要改动正则本身,依然保持了代码的可维护性。
测试验证
你可以用以下代码生成模拟测试文件(160万行分段数据):
perl6 -e ' my @terms = <iron copper carbon aluminum>; my $lines = 1608240; my $section-count = $lines div 20; # 每个分段20行左右 spurt "/tmp/DataRaw", join "\n", gather { for 1..$section-count { my $term = @terms.pick; take "beginSection $term"; take "random line $_" for 1..18; take "doneSection"; } # 补全剩余行数 take "extra line" for 1..($lines - $section-count*20); } '
运行改进后的代码,你会发现速度和显式写的正则几乎一致,每秒可以处理数千行,顺利完成任务。
额外优化建议
- 如果目标词汇数量很大,Perl 6会自动把
rx:i/ @target-terms /的多个备选字符串优化成trie结构,匹配效率比逐个||更高。 - 把
:i(忽略大小写)放到预编译的正则里,而不是每次匹配都指定,减少重复处理的开销。
内容的提问来源于stack exchange,提问作者lisprogtor
相关产品推荐
相关产品推荐

