Edited at

Golangの正規表現がどれぐらいおそいのかをPythonと比較してみた話

More than 1 year has passed since last update.

Screenshot from 2018-04-14 16-55-04.png

最近Packetbeatのプラグインを作っている中でGolangを書いてますが、Golangといえば正規表現が遅いとよく聞きますがほんとに遅いのかを試してみました。

今回はみんな大好きPythonと比較。

きっと言語それぞれに得意なケース、得意でないケースがあるかとは思いますが、

あくまでも実用コードの比較ということで・・・


入出力

SIPのname-addrをパースする正規表現の実行速度を比較したいと思いいます。

"Display-name"<sip:0312341234@10.0.0.1:5060>;tag=tag12345678といった入力の時に以下のように分解する正規表現を試します。


  • Display-name

  • sip

  • 0312341234

  • 10.0.0.1

  • 5060

  • tag=tag12345678


Golangのコード

package main

import (
"fmt"
"regexp"
)

func parseSIPAddrRegexp(addr string) {
rep := regexp.MustCompile(`^["]{0,1}([^"]*)["]{0,1}[ ]*<(sip|tel|sips):(([^@]*)@){0,1}([^>^:]*|\[[a-fA-F0-9:]*\]):{0,1}([0-9]*){0,1}>(;.*){0,1}$`)
result := rep.FindSubmatch([]byte(addr))
fmt.Printf("%s\n",result)
}

func main(){
name_addrs:=[]string{"\"display_name\"<sip:0312341234@10.0.0.1:5060>;user=phone;hogehoge",
"<sip:0312341234@10.0.0.1>",
"\"display_name\"<sip:0312341234@10.0.0.1>",
"<sip:whois.this>;user=phone",
"\"0333334444\"<sip:[2001:30:fe::4:123]>;user=phone"}
for i:=0; i< 100000 ; i ++{
for idx:= range name_addrs{
parseSIPAddrRegexp(name_addrs[idx])
}
}
}

繰り返し回数が1回の場合の出力結果は以下の通り

["display_name"<sip:0312341234@10.0.0.1:5060>;user=phone;hogehoge display_name sip 0312341234@ 0312341234 10.0.0.1 5060 ;user=phone;hogehoge]

[<sip:0312341234@10.0.0.1> sip 0312341234@ 0312341234 10.0.0.1 ]
["display_name"<sip:0312341234@10.0.0.1> display_name sip 0312341234@ 0312341234 10.0.0.1 ]
[<sip:whois.this>;user=phone sip whois.this ;user=phone]
["0333334444"<sip:[2001:30:fe::4:123]>;user=phone 0333334444 sip [2001:30:fe::4:123] ;user=phone]


Pythonのコード

#!/usr/bin/env python3

# -*- coding:utf-8 -*-

import re

def parseSIPAddrRegexp(addr):
rep = re.compile(r'^["]{0,1}([^"]*)["]{0,1}[ ]*<(sip|tel|sips):(([^@]*)@){0,1}([^>^:]*|\[[a-fA-F0-9:]*\]):{0,1}([0-9]*){0,1}>(;.*){0,1}$')
result=rep.match(addr)
print(result.groups())

if __name__ == "__main__":
name_addrs=["\"display_name\"<sip:0312341234@10.0.0.1:5060>;user=phone;hogehoge",
"<sip:0312341234@10.0.0.1>",
"\"display_name\"<sip:0312341234@10.0.0.1>",
"<sip:whois.this>;user=phone",
"\"0333334444\"<sip:[2001:30:fe::4:123]>;user=phone"]

for i in range(100000):
for idx in range(len(name_addrs)):
parseSIPAddrRegexp(name_addrs[idx])

繰り返し回数が1回の場合の出力結果は以下の通り

('display_name', 'sip', '0312341234@', '0312341234', '10.0.0.1', '5060', ';user=phone;hogehoge')

('', 'sip', '0312341234@', '0312341234', '10.0.0.1', '', None)
('display_name', 'sip', '0312341234@', '0312341234', '10.0.0.1', '', None)
('', 'sip', None, None, 'whois.this', '', ';user=phone')


測定

上記コードはともに100,000回同じ処理を繰り替えすように組んでいるので、実行時にtimeコマンドを使いcpu totalを計測したいと思います。計測は5回行い、その最大値、中央値、平均値、最小値を比較します。

また、標準出力はすべて/dev/nullにすべて捨てるようにします。


Golangの実行結果

$ for i in `seq 5`;do time go run regexp_sip.go >/dev/null ;done  

go run regexp_sip.go > /dev/null 27.06s user 1.15s system 111% cpu 25.359 total
go run regexp_sip.go > /dev/null 27.18s user 1.01s system 110% cpu 25.497 total
go run regexp_sip.go > /dev/null 27.33s user 1.24s system 110% cpu 25.943 total
go run regexp_sip.go > /dev/null 26.90s user 1.12s system 110% cpu 25.251 total
go run regexp_sip.go > /dev/null 27.26s user 1.12s system 110% cpu 25.793 total


Pythonの実行結果

$ for i in `seq 5`;do time python3 regexp_sip.py >/dev/null ;done

python3 regexp_sip.py > /dev/null 3.78s user 0.02s system 99% cpu 3.798 total
python3 regexp_sip.py > /dev/null 3.44s user 0.00s system 99% cpu 3.448 total
python3 regexp_sip.py > /dev/null 3.85s user 0.00s system 99% cpu 3.856 total
python3 regexp_sip.py > /dev/null 3.54s user 0.02s system 99% cpu 3.561 total
python3 regexp_sip.py > /dev/null 3.48s user 0.00s system 99% cpu 3.481 total


比較結果

まさかこんなに違うとは正直思っていなかったです・・・

Golang
Python

最大値
25.943
3.856

中央値
25.497
3.561

平均値
25.5686
3.6288

最小値
25.251
3.448


[2018/04/14 21:20追記]


どちらかというと、「正規表現のコンパイル速度」をはかっているように見えますが・・・


というコメントをいただき、なるほど、たしかにそういうことか・・・ということで再度検証しました。


[23:11追記]

コンパイルつどつどしてるのを突っ込まれそうでしたのでビルド済みで測定しなおしておきます。

コンパイル済みの正規表現を渡すように改変しています。


Go版

package main

import (
"fmt"
"regexp"
)

func parseSIPAddrRegexp(addr string, rep *regexp.Regexp) {
result := rep.FindSubmatch([]byte(addr))
fmt.Printf("%s\n",result)
}

func main(){
name_addrs:=[]string{"\"display_name\"<sip:0312341234@10.0.0.1:5060>;user=phone;hogehoge",
"<sip:0312341234@10.0.0.1>",
"\"display_name\"<sip:0312341234@10.0.0.1>",
"<sip:whois.this>;user=phone",
"\"0333334444\"<sip:[2001:30:fe::4:123]>;user=phone"}

rep := regexp.MustCompile(`^["]{0,1}([^"]*)["]{0,1}[ ]*<(sip|tel|sips):(([^@]*)@){0,1}([^>^:]*|\[[a-fA-F0-9:]*\]):{0,1}([0-9]*){0,1}>(;.*){0,1}$`)
for i:=0; i< 100000 ; i ++{
for idx:= range name_addrs{
parseSIPAddrRegexp(name_addrs[idx],rep)
}
}


実行結果

$ for i in `seq 5`;do time ./regexp_sip >/dev/null ;done      

./regexp_sip > /dev/null 4.44s user 0.15s system 100% cpu 4.563 total
./regexp_sip > /dev/null 4.42s user 0.20s system 100% cpu 4.588 total
./regexp_sip > /dev/null 4.50s user 0.14s system 100% cpu 4.613 total
./regexp_sip > /dev/null 4.47s user 0.16s system 100% cpu 4.611 total
./regexp_sip > /dev/null 4.46s user 0.20s system 100% cpu 4.640 total


Python版

#!/usr/bin/env python3

# -*- coding:utf-8 -*-

import re

def parseSIPAddrRegexp(addr,rep):
result=rep.match(addr)
print(result.groups())

if __name__ == "__main__":
name_addrs=["\"display_name\"<sip:0312341234@10.0.0.1:5060>;user=phone;hogehoge",
"<sip:0312341234@10.0.0.1>",
"\"display_name\"<sip:0312341234@10.0.0.1>",
"<sip:whois.this>;user=phone",
"\"0333334444\"<sip:[2001:30:fe::4:123]>;user=phone"]

rep = re.compile(r'^["]{0,1}([^"]*)["]{0,1}[ ]*<(sip|tel|sips):(([^@]*)@){0,1}([^>^:]*|\[[a-fA-F0-9:]*\]):{0,1}([0-9]*){0,1}>(;.*){0,1}$')
for i in range(100000):
for idx in range(len(name_addrs)):
parseSIPAddrRegexp(name_addrs[idx],rep)


実行結果

$ for i in `seq 5`;do time python3 regexp_sip.py >/dev/null ;done

python3 regexp_sip.py > /dev/null 3.02s user 0.00s system 99% cpu 3.039 total
python3 regexp_sip.py > /dev/null 2.92s user 0.00s system 99% cpu 2.928 total
python3 regexp_sip.py > /dev/null 2.99s user 0.01s system 99% cpu 3.014 total
python3 regexp_sip.py > /dev/null 2.96s user 0.01s system 99% cpu 2.983 total
python3 regexp_sip.py > /dev/null 3.09s user 0.01s system 99% cpu 3.106 total


比較

Golang
Python

最大値
4.64
3.106

中央値
4.611
3.014

平均値
4.603
3.014

最小値
4.563
2.928

ぐっと縮まりましたが、まだそれでもGolangのほうが遅いですね。

Pythonはいちいちコンパイルしなくても速いというのをどこかで読んだことありますが、ほか言語にそれを当てはめてコーディングするとダメな例ですね。ご指摘ありがとうございました!