#match #string #optimization #trie #byte

无 std lighter-derive

lighter 库的进程宏

1 个不稳定版本

0.1.0 2022年2月28日

#52#bytes


用于 lighter

CC0 许可证

13KB
195

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 将扁平的匹配语句转换成静态的 字典树,其中每个匹配语句考虑一次一个字符

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 使用他们全部的优化工具来映射数字到数字的开关,从而产生 更好的代码。嵌套的 match 结构还意味着我们只需要比较每个字符一次:使用普通的 matchgreeting_id 会将其输入与 "hi" 中的 h"hello" 中的 h 进行比较,而使用 lightergreeting_id 只匹配一次 h,并知道它正在寻找的后缀是 iello

更重要的是,lighter 不仅与字符串或切片一起工作:您还可以对任何 u8 迭代器的输出进行匹配,或者任何可以转换为迭代器的东西。 lighter 还可以匹配与整个字符串匹配的字符串前缀,或者除了整个字符串外还匹配字符串;参见 lighter/examples/is_whitespace_2.rs

依赖项

~3.5MB
~75K SLoC