Go 重構筆記 3 - Regexp performance
Posted

很久沒寫文章了,雖然靈感很多,但最近因為新專案上線,和做一些有的沒的 side project,真的沒空整理資料和寫文章 都是藉口,所以上來刷個存在感,touch 一下 blog 的最後更新時間。
Background
專案有一段很長的 Regular expression 在判斷字串符不符合規則。
package main
import "regexp"
const p = `^([A-Za-z0-9_.-]+|[a-z0-9._%+\-][email protected][a-z0-9.\-]+\.[a-z]{2,4})$`
func validate(s string) (bool, error) {
return regexp.MatchString(p, s)
}
測了一下才發現他的 performance 極差,只要每做一次,就會多 alloc 81 次記憶體,增加了很多 GC 壓力。
package main
import "testing"
func BenchmarkValidate(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = validate("foo")
}
}
$ go test -bench=. -benchmem -benchtime=3s -run=none
BenchmarkValidate-4 311816 12025 ns/op 9246 B/op 81 allocs/op
Solution
可以使用 regexp.Compile()
讓它只 compile 一次就好,不需要每次 call function 都處理一次 regexp pattern。
package main
import "regexp"
const p = `^([A-Za-z0-9_.-]+|[a-z0-9._%+\-][email protected][a-z0-9.\-]+\.[a-z]{2,4})$`
func validate(s string) (bool, error) {
return regexp.MatchString(p, s)
}
var r = regexp.MustCompile(p)
func validate2(s string) bool {
return r.MatchString(s)
}
我們再測一次發現,效能差了好幾倍,本來會多 alloc 81 次記憶體變成 0 次,速度也快了 50 幾倍。
package main
import "testing"
func BenchmarkValidate(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = validate("foo")
}
}
func BenchmarkValidate2(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = validate2("foo")
}
}
$ go test -bench=. -benchmem -benchtime=3s -run=none
BenchmarkValidate-4 298816 11231 ns/op 9241 B/op 81 allocs/op
BenchmarkValidate2-4 16597316 215 ns/op 0 B/op 0 allocs/op
後記
其實這是在學 Java 發現的 tips,剛好目前專案有遇到這個問題,很多地方都可以用這種方式處理,避免創建多次不必要的 instance,只在 compile 的時候做一次,多 reuse memory,可參考 Fasthttp best practices,當然因為都共用同一個 memory address,也要多考慮會不會發生 race condition。
掰。