在 Rust 中优化一个数学表达式解析器

主要内容总结:

  • 优化数学表达式解析器:在 Rust 中编写数学表达式解析器并优化其速度和内存效率,以处理包含加法、减法和括号的简单数学表达式,如4 + 5 + 2 - 1等。
  • 基线实现(43.1s)

    • 程序读取input.txt文件中的数学表达式,通过tokenize函数将输入字符串分割成标记,eval函数处理表达式,parse_expressionparse_primary等函数递归地解析表达式。
    • 例如解析(1 + 2) - 3,通过多个函数的调用和递归处理,最终得出结果0
    • 此基线解析器虽能工作,但未优化,在 1.5GB 的测试文件上需 43.87 秒。
  • 优化步骤及效果

    • 优化 1:避免分配向量:使用cargo flamegraphdhat分析发现大部分时间花费在tokenizer函数的字符串分配上,将tokenize函数改为直接返回迭代器,避免分配向量,速度从 43.1s 提升至 6.45s,减少 85%。
    • 优化 2:直接解析输入字节:分析火焰图发现虽避免了向量分配,但仍有字符串处理开销,改用&[u8]和手动扫描字节来避免临时字符串分配,速度从 6.45s 降至 3.68s,减少 43%。
    • 优化 3:不使用 Peekable:发现Peekable导致性能开销,将代码中的Peekable移除,直接使用普通迭代器处理令牌,速度从 3.68s 降至 3.21s,减少 13%。
    • 优化 4:多线程和 SIMD:由于数学和语法规则限制,不能简单地将文件分割成 8 等份并行计算,使用 SIMD 找到最接近的有效分割点,然后并行处理每个块,速度从 3.21s 降至 2.21s,减少 31%。
    • 优化 5:内存映射 I/O:分析内存使用情况发现仍在堆上分配大缓冲区,使用mmap避免额外的内存复制和虚假共享争用,速度从 2.21s 降至 0.98s,减少 56%。
  • 结论:通过一系列优化,将简单数学表达式解析器的运行时间从 43 秒降低至不到 1 秒,包括避免一次性创建标记列表、处理字节而不是文本、简化代码、使用多线程和 SIMD 以及使用内存映射文件等操作。可在https://github.com/RPallas92/math_parser找到完整代码。
阅读 20
0 条评论