正则性能优化
最近在编码时用到了golang(版本为1.11.0
)正则库中的ReplaceAllLiteral(data, nil)
函数,发现golang的正则替换比java和php的正则替换慢很多,后来找到了方法,可以大幅度提升golang正则的性能,文章后面部分有具体的对比数据。
golang正则为什么比php慢
我并不是第一个发现golang的正则库比php慢的,因为在golang的开源社区中有人已经提出了类似的问题,见issues,有人回复说:像php、python这样的语言的正则引擎,是用高度优化的C代码实现的,所以比较快。好吧,但java也比golang快至少可以说明golang的正则引擎还是有优化的空间。
提升golang正则性能
后来我现了一个叫rure-go
的项目,详见 github.com,这个项目是一个用Rust语言实现的golang正则引擎,原理是先将Rust语言的正则库编译为一个.so动态库,然后用golang语言封装出来一套正则处理函数去调用.so动态库中的函数。
具体步骤:
先安装Rust:
curl https://sh.rustup.rs -sSf | sh
将Rust的正则库编译为动态库(Rust支持交叉编译):
git clone git://github.com/rust-lang-nursery/regex cargo build --release --manifest-path ./regex/regex-capi/Cargo.toml
这时在
./regex/target/release/
目录下会出现一个.so文件,把它放到你喜欢地方,比如在你的golang项目下新建一个./lib
目录,然后就可以删掉Rust相关的东西了。在你的golang项目中引入
rure-go
项目,然后编写你的业务逻辑,注意这个包中没有ReplaceAll()
这样的函数,所以要达到ReplaceAll(data, nil)
的效果,需要先用FindAll()
在data中找到所有匹配的位置,然后把所有不匹配的内容提取出来拼到一起。编译和运行时需要指定动态库的位置,因为
github.com/BurntSushi/rure-go
会依赖它,注意,当指定了动态库后就不能使用go的交叉编译了:如果你的电脑是MAC 编译 CGO_LDFLAGS="-L$(pwd)/lib" go build ./your/project/main 启动 DYLD_LIBRARY_PATH=./lib ./main 如果你的电脑是Linux 编译 CGO_LDFLAGS="-L$(pwd)/lib" go build ./your/project/main 启动 LD_LIBRARY_PATH=./lib ./main
性能对比
对比php
调用次数 | php用时(秒) | go优化前(秒) | go优化后(秒) |
---|---|---|---|
100 | 3.119 | 未记录 | 1.864 |
1000 | 27.115 | 45.285 | 16.475 |
5000 | 135.88 | 未记录 | 82.42 |
对比java
调用次数 | java用时(秒) | go优化前(秒) | go优化后(秒) |
---|---|---|---|
1000 | 5.210 | 9.399 | 1.155 |
2000 | 10.652 | 18.844 | 2.286 |
10000 | 50.156 | 93.974 | 11.163 |
总结
golang标准库中的正则处理引擎性能比较差,如果遇到对性能要求较高的需求,可以用github.com/BurntSushi/rure-go
包代替,它可以大幅提升正则处理性能。
附一个rure-go库的使用示例:
import (
...
rure "github.com/BurntSushi/rure-go"
)
func main() {
var data []byte
var buf = bytes.NewBuffer(make([]byte, 0, len(data)))
var reg = rure.MustCompile(`(?is)<table[^>]*class="box2"[^>]*>`)
index := reg.FindAllBytes(data)
data = deleteMatch(data, index, buf) // 删掉匹配到的内容
}
func deleteMatch(data []byte, idx []int, buf *bytes.Buffer) []byte {
if len(idx) < 1 {
return data
}
buf.Reset()
buf.Write(data[0:idx[0]])
for i := 1; i < len(idx); i += 2 {
if i+1 >= len(idx) {
start := idx[i]
buf.Write(data[start:])
} else {
start := idx[i]
end := idx[i+1]
buf.Write(data[start:end])
}
}
data = buf.Bytes()
return data
}