所有文章

正则性能优化

最近在编码时用到了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
}

编写日期:2019-01-06