#match #string #optimization #byte #trie

无std lighter

将字符串匹配重写为Trie的宏

1个不稳定版本

0.1.0 2022年2月28日

#302解析工具

CC0 协议

5KB
69

lighter - 略好于普通的 match 🔥

假设我们有一些使用字符串匹配语句的Rust代码

pub fn greeting_id(greeting: &str) -> usize {
    match greeting {
        "hi" => 0,
        "hey" => 1,
        "hello" => 2,
        _ => 3,
    }
}

结果发现,对于字符串模式的匹配语句被编译成一系列的if语句,上述代码本质上等同于以下内容

pub fn greeting_id(greeting: &str) -> usize {
    if greeting == "hi" {
        0
    } else if greeting == "hey" {
        1
    } else if greeting == "hello" {
        2
    } else {
        3
    }
}

rustc 和 LLVM 有许多技巧来优化匹配语句,其中参数是原始类型,例如 整数枚举,但Rust似乎并不将字符串视为匹配语句中“特殊”的数字或枚举,而是回退到调用相关的PartialEq实现。这可能效率低下,特别是如果我们有很多字符串并且其中一些共享公共前缀时。以下是使用lighter而不是普通match修改的greeting_id

use lighter::lighter;

pub fn greeting_id(greeting: &str) -> usize {
    lighter! {
        match greeting {
            "hi" => 0,
            "hey" => 1,
            "hello" => 2,
            _ => 3,
        }
    }
}

在编译过程中,lighter基本上将扁平的匹配语句转换为静态的Trie,其中每个匹配语句考虑一个字符

pub fn greeting_id(greeting: &str) -> usize {
    let mut bytes = greeting.bytes();
    match bytes.next() {
        Some(b'h') => match bytes.next() {
            Some(b'i') => match bytes.next() {
                None => 0,
                _ => 3,
            },
            Some(b'e') => match bytes.next() {
                Some(b'y') => match bytes.next() {
                    None => 1,
                    _ => 3,
                },
                Some(b'l') => match bytes.next() {
                    Some(b'l') => match bytes.next() {
                        Some(b'o') => match bytes.next() {
                            None => 2,
                            _ => 3,
                        },
                        _ => 3,
                    },
                    _ => 3,
                },
                _ => 3,
            },
            _ => 3,
        },
        _ => 3,
    }
}

与没有使用lighter的原始match相比,这可能会显得有些复杂,但通过使用字节字面量(实际上只是u8),我们允许Rust和LLVM使用它们全部的优化功能来处理数字到数字的映射,从而得到更好的代码。[链接](https://rust.godbolt.org/z/zcxKhdWfd "更好的代码")。嵌套的match结构也意味着我们只需要比较每个字符一次:使用普通的matchgreeting_id会将输入与字符串""hi""和""hello""中的"h"进行比较,而使用lighter时,greeting_id只需匹配一次"h",并知道它要查找的后缀是"i"或""ello""。

更重要的是,lighter不仅与字符串或切片一起工作:你可以匹配任何u8迭代器的输出,或者任何可以被转换成迭代器的对象。lighter还可以匹配与整个字符串匹配的字符串前缀,或者作为整个字符串的补充;请参阅lighter/examples/is_whitespace_2.rs

依赖项

~3.5MB
~75K SLoC