You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.
# 警惕 UTF-8 引发的性能隐患
大家应该都知道, 虽然 Rust 的字符串 `&str` 、`String` 在底层是通过 `Vec<u8>` 实现的:字符串数据以字节数组的形式存在堆上,但在使用时,它们都是 UTF-8 编码的,例如:
```rust
fn main () {
let s : & str = "中国人" ;
for c in s . chars () {
println! ( "{}" , c ) // 依次输出:中 、 国 、 人
}
let c = & s [ 0 .. 3 ]; // 1. "中" 在 UTF-8 中占用 3 个字节 2. Rust 不支持字符串索引,因此只能通过切片的方式获取 "中"
assert_eq! ( c , "中" );
}
```
从上述代码, 可以很清晰看出, Rust 的字符串确实是 UTF-8 编码的,这就带来一个隐患:可能在某个转角,你就会遇到来自糟糕性能的示爱。
## 问题描述 & 解决
例如我们尝试写一个词法解析器,里面用到了以下代码 `self.source.chars().nth(self.index).unwrap();` 去获取下一个需要处理的字符,大家可能会以为 `.nth` 的访问应该非常快吧?事实上它确实很快,但是并不妨碍这段代码在循环处理 70000 长度的字符串时,需要消耗 5s 才能完成!
这么看来,唯一的问题就在于 `.chars()` 上了。
其实原因很简单,简单到我们不需要用代码来说明,只需要文字描述即可传达足够的力量:每一次循环时,`.chars().nth(index)` 都需要对字符串进行一次 UTF-8 解析,这个解析实际上是相当昂贵的,特别是当配合循环时,算法的复杂度就是平方级的。
既然找到原因,那解决方法也很简单:只要将 `self.source.chars()` 的迭代器存储起来就行,这样每次 `.nth` 调用都会复用已经解析好的迭代器,而不是重新去解析一次 UTF-8 字符串。
当然,我们还可以使用三方库来解决这个问题,例如 [str_indices ](https://crates.io/crates/str_indices )。
## 总结
最终的优化结果如下:
- 保存迭代器后: 耗时 `5s` -> `4ms`
- 进一步使用 `u8` 字节数组来替换 `char` ,最后使用 `String::from_utf8` 来构建 UTF-8 字符串: 耗时 `4ms` -> `400us`
** 肉眼可见的巨大提升, 12500 倍!**
总之,我们在热点路径中使用字符串做 UTF-8 的相关操作时,就算不提前优化,也要做到心里有数,这样才能在问题发生时,进退自如。