【问题标题】:Save/load compiled Regex in Rust?在 Rust 中保存/加载编译的正则表达式?
【发布时间】:2021-03-24 12:17:01
【问题描述】:

我正在开发一些性能至关重要的应用程序,我正在寻找改进它的机会。该应用广泛使用正则表达式(regex crate),例如:

use regex::Regex;
...
let Regex = Regex::new(r###"[a-z0-9%]{3,}"###).unwrap();

有没有机会将预编译的Regex 实例保存到缓冲区(想想Vec<u8>)并在以后加载预编译以避免多次编译(在docs 中看不到类似的东西)?

PS。我已经懒惰地这样做了:

    lazy_static! {
        static ref REGEX2: Regex = Regex::new(r###"[a-z0-9%]{3,}"###).unwrap();
    }

如果有多次调用,尝试节省一些 CPU 周期(但这是另一回事)。

PS。出于好奇,我对代码进行了基准测试:

fn bench_regex_candidates(b: &mut Bencher) {
    b.iter(|| {
        Regex::new(r###"[a-z0-9%]{3,}"###).unwrap();
    });
}

fn bench_regex_content(b: &mut Bencher) {
    b.iter(|| {
        Regex::new(r###"^([^*|@"!]*?)#([@?$])?#(.+)$"###).unwrap()
    });
}

而且编译速度很快(相对来说,满足我的需要):

regex/candidates        time:   [22.021 us 22.684 us 23.689 us]                              
Found 11 outliers among 100 measurements (11.00%)
  2 (2.00%) high mild
  9 (9.00%) high severe
regex/content           time:   [36.542 us 36.687 us 36.845 us]                           
Found 8 outliers among 100 measurements (8.00%)
  3 (3.00%) high mild
  5 (5.00%) high severe

【问题讨论】:

  • 这确实是一个有趣的问题。我认为没有一种好的方法可以做到这一点而不会招致一些不安全的编年史。让我们看看是否有人给我们惊喜!
  • 您的构建时间是否受到正则表达式编译的负面影响?
  • @tadman 这与构建时间无关,而是与应用程序运行时间有关 - 正则表达式可以编译一次,然后重用,甚至预编译并捆绑到应用程序中。正则表达式也可以在运行时出现,因此我们可以分发预编译的正则表达式而不是文本表示
  • 你有几个?除非它 > 10K,否则我几乎不怀疑您甚至可以测量实例化它们所需的时间。在探索这样的性能问题时,它可以帮助你故意让你的代码真正“愚蠢”,比如做比你需要的多 100 倍的工作,只是为了看看它是否真的减慢了任何速度。通常不会。
  • 客观上我认为这里的解决方案是不使用正则表达式,而是将自己解析为代码。这总是更快,但要正确实施可能需要更多的麻烦。

标签: regex serialization rust deserialization


【解决方案1】:

简短的回答是不,你不能。 regex crate 本身必须提供类似的东西。有一个未解决的问题跟踪它:https://github.com/rust-lang/regex/issues/258

短期内不太可能支持这样的功能。一般来说,regex crate 有两种方法可以解决这个问题。第一种是使用 Serde 简单地序列化其内部数据结构。第二个是使内部实现与定制的序列化结构一致。

在第一种情况下,naive 方法的实现工作可能并不算太糟糕,并且可能通过在几个地方散布一些derive(serde::Serialize, serde::Deserialize) 来完成。这种方法有两个主要问题。首先,它需要一个显式的序列化步骤,这将有其自身的成本,并且在足够多的情况下它是否会比正则表达式编译本身更快还不清楚。其次,这会将内部数据结构公开为公共 API 保证。所以实际上,实现这一点可能需要选择一种我们可以承诺的格式。反过来,这可能需要在序列化之前和反序列化之后再进行一个翻译步骤,从而增加成本。所以总的来说,这种方法需要付出相当大的努力,而且还不清楚它是否值得。

第二种方法将使序列化和反序列化有效地免费,因为内部表示将是序列化形式。这受到与第一个问题相同的“公共 API”格式保证的影响,因此必须仔细完成以管理兼容性保证。这种方法的另一个主要问题是它会感染整个内部实现。实际上,在编写所有内容时都必须牢记这种约束。例如,不能再使用内部指针。因此,要做到这一点,需要在代码复杂性上做出很大的权衡。而且工作量也很大。

其他人提到了对您的代码进行基准测试。你应该这样做,如果你能仔细证明为什么需要消除编译成本,这个问题将是一个更好的问题。仅仅因为您正在编写一个高性能应用程序并且您正在使用在运行时使用模式字符串编译的正则表达式并不意味着正则表达式编译本身就是一个瓶颈。绝对可能是,但也很可能不是。因此,检查这个假设非常重要。理想情况下,您应该创建一个最小的基准程序来重现您的应用程序的相同使用模式,然后衡量您的程序的哪些方面大部分时间都在使用。

话虽如此,regex-automata crate does support (de)serializing regexes,它使用我上面概述的第二种方法来实现。但是,regex-automata 不是通用正则表达式引擎,there are severe differences between it and the regex crate。例如,不适合使用regex-automata 来编译带有从用户输入派生的模式字符串的正则表达式,因为编译可能会花费模式大小的指数时间。

【讨论】:

    猜你喜欢
    • 2010-09-17
    • 2013-01-23
    • 1970-01-01
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    • 2010-09-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多