pperl:进行正则表达式即时编译

💡 原文英文,约1300词,阅读约需5分钟。
📝

内容提要

本文介绍Perl JIT编译器pperl的新组件ReJIT,旨在提升正则表达式性能。在regex-redux基准测试中,pperl因解释器架构落后于V8的Irregexp编译器,故ReJIT将正则表达式编译为机器码,采用PCRE2风格,编译perl引擎的regnode程序,保留语义,支持捕获,并分阶段实施,预计减少3-5倍指令数。

🔎

延伸解读

为什么解释器架构是性能瓶颈

文章指出,pperl在regex-redux基准测试中落后于Node.js的主要原因并非Perl JIT不够快,而是正则表达式引擎本身采用解释执行。即使JIT编译了Perl操作码,超过99%的指令仍消耗在匹配操作内部,因此无法从操作码层面优化。这解释了为何需要专门的正则表达式编译器ReJIT来直接生成机器码。

ReJIT的设计选择:PCRE2风格而非RE2

ReJIT选择编译Perl引擎自身的regnode程序,而非像RE2那样构建线性自动机。原因在于Perl的正则语义(如捕获组、回溯顺序)是可见行为,线性引擎无法表达。PCRE2风格允许保留完整语义,同时通过编译提升性能,并在无法编译时回退到解释器,确保兼容性。

性能优化的重点:类开头交替

文章通过指令统计发现,基准测试中约55%的指令消耗在四个以字符类开头的交替模式上,因为Perl优化器无法为它们构建trie。ReJIT的首要目标正是将这些模式编译为机器码扫描循环,预计可减少3-5倍指令数。这体现了针对实际热点进行优化的策略。

语义一致性的严格保证

ReJIT强调与Perl语义逐字节一致,并采用差分测试机制:每次编译匹配都会与解释引擎的结果对比,包括完整捕获状态。文章举例说明Perl与ECMAScript在捕获组处理上的差异,若直接复制Irregexp的寄存器清理逻辑会导致错误。这种机械式验证确保了编译器的正确性。

Q&A

什么是pperl的ReJIT组件?

ReJIT是pperl的一个新组件,用于将正则表达式编译为机器码,以提升正则表达式的性能。它采用PCRE2风格,编译perl引擎的regnode程序,保留语义,支持捕获,并分阶段实施。

为什么pperl在regex-redux基准测试中落后于V8?

因为V8使用Irregexp编译器将正则表达式直接编译为机器码,而pperl使用解释器执行正则表达式,导致指令数相差约7倍。pperl的JIT编译器只编译Perl操作码,无法优化正则表达式引擎内部的执行。

ReJIT如何编译正则表达式?

ReJIT采用PCRE2风格,编译perl引擎的regnode程序(即编译后的正则表达式字节码)为机器码,使用Cranelift后端。它只编译引擎接受的模式,不接受的模式回退到解释器。

为什么ReJIT不采用RE2风格的线性引擎?

因为perl已经有一个预过滤层(子串和起始类预搜索),重复实现没有意义;而且线性引擎无法表达perl的回溯顺序语义,如分支优先级、捕获组的值以及(*SKIP)等行为,这些在Perl中是可见的行为。

ReJIT如何保证与Perl语义一致?

ReJIT通过差分测试保证语义一致:在测试模式下,每个编译后的匹配都会通过原始引擎重新运行并比较结果,包括完整的捕获状态。此外,它拒绝编译无法处理的Perl特定机制,如代码块、模式递归等。

ReJIT的实施阶段有哪些?

ReJIT分阶段实施:首先添加仪器和差分测试,然后实现扫描循环层,接着处理量词深度,最后处理UTF-8轴。

ReJIT预计能带来多大的性能提升?

对于扫描循环层,预计在regex-redux基准测试中能减少3-5倍的指令数。

🏷️

标签

➡️

继续阅读