正则性能优化#

最近在编码时用到了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动态库中的函数。

具体步骤:

  1. 先安装Rust:

    curl https://sh.rustup.rs -sSf | sh
  2. 将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相关的东西了。

  3. 在你的golang项目中引入rure-go项目,然后编写你的业务逻辑,注意这个包中没有ReplaceAll()这样的函数,所以要达到ReplaceAll(data, nil)的效果,需要先用FindAll()在data中找到所有匹配的位置,然后把所有不匹配的内容提取出来拼到一起。

  4. 编译和运行时需要指定动态库的位置,因为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
}



本站所有作品均采用知识共享署名-非商业性使用-禁止演绎 3.0 中国大陆许可协议进行许可。