Golangで正規表現を使おうとなると、何も考えないとregexpなどを使ってしまいたくなりますが、他の方の記事(参考文献に記載)にもあるように速度がかなり遅くなってしまいます。
そこで、今回はGolangの正規表現のパターンとその速度を検証してみたいと思います。
比較の前提
まず用いるアルゴリズムですが、regexpによる正規表現、strings.Replacerによる置換を用います。また呼び出し方もregexpとstrings.Replacerはそれぞれ正規表現の宣言(それぞれMustCompileとNewReplacer)をループごとに呼び出すかという話があるので、
- regexpによる正規表現(MustCompile中/外)
- strings.Replacerによる正規表現(NewReplacer中/外)
の計5パターンを試してみたいと思います。
比較の方法については簡単な正規表現を用います。用途によっても色々あると思うのですが、今回は短文ですが以下の文章を使います。イメージとしてはメールの文章の個人名を正規表現で伏せるという用途を考えます。
田中さん、お元気ですか。今度の火曜日に鈴木さんと打ち合わせがあるのですがご都合いかがでしょうか。
これを
XXさん、お元気ですか。今度の火曜日にXXさんと打ち合わせがあるのですがご都合いかがでしょうか。
比較結果
package main
import (
"regexp"
"strings"
"testing"
)
// Compile抜きReplacer
func BenchmarkReplace(b *testing.B) {
str := "田中さん、お元気ですか。今度の火曜日に鈴木さんと打ち合わせがあるのですがご都合いかがでしょうか。"
rep := strings.NewReplacer("田中", "XX", "鈴木", "XX")
for n := 0; n < b.N; n++ {
_ = rep.Replace(str)
}
}
// Compile込みReplacer
func BenchmarkReplaceIn(b *testing.B) {
str := "田中さん、お元気ですか。今度の火曜日に鈴木さんと打ち合わせがあるのですがご都合いかがでしょうか。"
for n := 0; n < b.N; n++ {
rep := strings.NewReplacer("田中", "XX", "鈴木", "XX")
_ = rep.Replace(str)
}
}
// コンパイル抜きReg
func BenchmarkReplacereg(b *testing.B) {
str := "田中さん、お元気ですか。今度の火曜日に鈴木さんと打ち合わせがあるのですがご都合いかがでしょうか。"
rep := regexp.MustCompile("田中|鈴木")
for n := 0; n < b.N; n++ {
_ = rep.ReplaceAllString(str, "")
}
}
// コンパイル込みReg
func BenchmarkReplaceregIn(b *testing.B) {
str := "田中さん、お元気ですか。今度の火曜日に鈴木さんと打ち合わせがあるのですがご都合いかがでしょうか。"
for n := 0; n < b.N; n++ {
rep := regexp.MustCompile("田中|鈴木")
_ = rep.ReplaceAllString(str, "")
}
}
BenchmarkReplace-4 5000000 397 ns/op 320 B/op 3 allocs/op
BenchmarkReplaceIn-4 1000000 1229 ns/op 1104 B/op 10 allocs/op
BenchmarkReplacereg-4 500000 2446 ns/op 368 B/op 4 allocs/op
BenchmarkReplaceregIn-4 200000 8887 ns/op 39392 B/op 31 allocs/op
だいたい、コンパイルがありとなしでns/ops換算でそれぞれReplacerは3.1倍・Regexpでは3.6倍の差が出ました。
また、ReplacerとRegexpでは7倍程度の差が出ました。
この辺はたしかにReplacerの方が早いという結果なのですが、Replacerの定義をfor文の中で行っている場合でもコンパイルを外においているRegexpより倍近く早い結果となりました。
まとめ
今回はReplacerとRegexpの文字列置換についてまとめました。他の方々がやられているように、基本的にReplacerを使うのがベストとは思いますが、一方でstrings.NewReplacerの宣言部分をfor文の外に出すだけでも3倍近く早くなるという結果が得られました。
実際にコードに落とし込む際に、あまりに正規表現の宣言と利用部分がバラバラになっても管理しづらいケースがあるかもしれませんが、うまく使っていけばより早い速度向上に繋がるのかなと思ったりします。
参考文献
Golangの正規表現がどれぐらいおそいのかをPythonと比較してみた話