最近,宝玉在群里抛了一个 case,大意是说,因为 npm
更新了上游一个包,导致他们的服务性能下降明显。排查之后发现,上游把一个处理字符串的函数(用于将 &<>"
替换为相应的 HTML 转义)从类似 str.replace(/"/g, '"')
的写法,改成了循环遍历 str
,然后逐个字符检查,再用 +=
拼接到新的输出字符串上。
显然,这又是一个重新造的轮子不圆引发的问题。
为啥这么说呢?我不了解 JavaScript,但对 Python 有所了解。我找了一下 Python 对字符串 replace 的实现,一下就看明白了差距。
1 | // https://svn.python.org/projects/python/trunk/Objects/stringobject.c |
跳过边界检查,Python 的 C 实现当中,对 from_s
和 to_s
不同长度的情况作了不同的处理。当二者长度相同的时候,由于无需额外分配内存,可以使用 in-place 的方式解决问题。当二者长度不同时,若 from_s
的长度为 1,走特定优化的版本;否则,走最通用的版本。
对应到宝玉遇到的问题,显然落到了 from_s
长度为 1 的情形。我们继续再深入看一下。
1 | /* len(self)>=1, len(from)==1, len(to)>=2, maxcount>=1 */ |
不难看出,这是一个对特定情况的专门优化。
- 遍历原字符串,计数
from_c
出现的次数; - 计算目标字符串的长度
len(str) + count * (len(to_str) - 1)
,提前分配内存; - 循环地找 from_str 下一次出现的位置,然后 memcpy 原始字符串 + memcpy to_str。
这种操作,肯定会比不断用 +=
追加单个字符要快得多。因此有标题:Again, 不要重复造轮子。