利用UTF-8编码的特性进行优化

引子

做性能优化的时候,画出火焰图(async-profiler)来看,发现耗时很多花在了utf-8的编码解码上了,所以需要思考一下如何优化这部分。
(采样30秒:./profiler.sh -d 30 -f profile.svg [pid])

What: 什么是UTF-8编码

UTF-8编码就是对于字节流(一串二进制)的解释,解释成字符串。
类似的还有: ASCII编码,就是把1B字节解释成128种字符。(范围是0-127,包括英文字母、数字)

UTF-8编码下,一个字符可以由1B~4B二进制组成。

UTF-8格式

合法的格式包括4种:

1
2
3
4
0xxxxxxx 				                0-127 (ASCII)
110xxxxx 10xxxxxx 128-2047
1110xxxx 10xxxxxx 10xxxxxx 2048-65535
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff

它的首字节的前缀码会指出这个字符会占用多少字节。
合法的前缀码有4种:

1
2
3
4
0X:    占1B;
110X: 占2B;
1110: 占3B;
11110: 占4B;

显然还有一种10X不在其中。这是因为10X不属于合法的首字节的前缀码,只能在第2、3、4个字节中出现。如果我们随机选择utf-8二进制流中的一个字节来看的话,可能的情况有5种:

1
2
3
4
5
0X:     首字节,这个字符总长1B
10X: 非首字节.
110X: 首字节,这个字符总长2B
1110X: 首字节,这个字符总长3B
11110X: 首字节,这个字符总长4B

utf-8特性

  1. 兼容性: 0-127和ascii码是一样的,而且在其他字节不可能出现,因此和ascii完全兼容;
  2. 自同步性: 基于上面讨论的前缀码特性,从任意位置开始读,可以定位到合法位置开始解码。

优化方案

基于上述两点洞察,再考虑到我此次工作只需要解析字母数字部分,再直接分发二进制流即可。因此可以做两点优化:

  1. 读取时: 不解码,直接理解二进制的每个字节;
  2. 利用自同步性: 并发解析; 原先只能顺序解码,并发解析可以从任意位置开始;
  3. 输出时: 不编码,直接输出二进制;

结果

取消编码、解码后这部分耗时下降了12%;
并发处理则大幅提速,提速了一倍以上。

推荐文章