やりたいこと
Python の正規表現の re と regex とで正規表現のコンパイルとマッチングの性能をプロファイラの cProfile を使って比較する。
regex
インストール
$ pip install regex
regex の使い方
re を regex に置き換えれば regex による正規表現のマッチングを行うことができる。
$ python
>>> import regex
>>> p = regex.compile('def')
>>> p.search('abcdefghi')
regex.Match object; span=(3, 6), match='def'>
cProfile
利用イメージ
cProfile は python の標準のプロファイラでインストールすることなく利用することができる。
cProfile を使うと、関数毎に実行回数、1回あたりの平均実行時間、合計実行時間などを取得することができる。
以下のように計測したい処理の直前で enable() を実行し、計測したい処理の直後で disable() を実行すると
pstats でプロファイル結果を取得することができる。
cProfile の例
import cProfile, pstats, io
pr = cProfile.Profile()
pr.enable()
# 計測したい処理
do_task()
pr.disable()
# 結果出力
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats("cumulative") # or "tottime"
ps.print_stats(50) # 上位50件を出力
print(s.getvalue())
出力イメージ
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 2.214 2.214 /.../profile.py:46(main)
1 0.117 0.117 2.214 2.214 /.../profile.py:31(test)
- ncalls: その関数が呼び出された総回数。
- tottime: その関数自身の実行に費やされた合計時間(秒)で、その関数内でから呼び出された関数の実行時間は含まない。
- percall: tottime / ncalls の値で、その関数自身の1回あたりの平均実行時間。
- cumtime: その関数とその関数が内部で呼び出した全ての関数を含む合計実行時間(秒)。
cProfile による re と regex の比較
プログラム
以下のプログラムで re と regex の比較を行う。
test1.py
import re
import regex
import cProfile, pstats, io
def compile_re(pttrn_str):
pttrn = re.compile(pttrn_str)
return pttrn
def compile_regex(pttrn_str):
pttrn = regex.compile(pttrn_str)
return pttrn
def search_re(text, pttrn):
m = pttrn.search(text)
return m
def search_regex(text, pttrn):
m = pttrn.search(text)
return m
def test(num):
text = 'a' * 1000 + '0' * 1000
for i in range(0, num):
pttrn_str = 'a' * 50 + '0' * 50
pttrn_re = compile_re(pttrn_str)
search_re(text, pttrn_re)
pttrn_regex = compile_regex(pttrn_str)
search_regex(text, pttrn_regex)
def main():
test(100000)
return 0
if __name__ == '__main__':
pr = cProfile.Profile()
pr.enable()
# 計測したい処理
res = main()
pr.disable
# 出力
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats("cumulative") or "tottime"
ps.print_stats(50) # 上位50件
print(s.getvalue())
exit(res)
実行結果
プログラムの出力結果は以下のようになった。
実行結果(抜粋)
$ python test1.py
3803513 function calls (3803512 primitive calls) in 2.214 seconds
Ordered by: cumulative time
List reduced from 93 to 50 due to restriction <50>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 2.214 2.214 /.../test1.py:46(main)
1 0.117 0.117 2.214 2.214 /.../test1.py:31(test)
100000 0.044 0.000 1.441 0.000 /.../test1.py:16(compile_regex) ★
100000 0.075 0.000 1.397 0.000 /.../lib/python3.12/site-packages/regex/_main.py:349(compile)
100000 0.323 0.000 1.321 0.000 /.../lib/python3.12/site-packages/regex/_main.py:449(_compile)
100000 0.035 0.000 0.367 0.000 /.../test1.py:21(search_re) ★
100000 0.037 0.000 0.149 0.000 /.../test1.py:11(compile_re) ★
100000 0.038 0.000 0.140 0.000 /.../test1.py:26(search_regex) ★
200118 0.083 0.000 0.136 0.000 /.../lib/python3.12/enum.py:726(__call__)
100000 0.034 0.000 0.111 0.000 /.../lib/python3.12/re/__init__.py:226(compile)
100000 0.049 0.000 0.078 0.000 /.../lib/python3.12/re/__init__.py:280(_compile)
100000 0.055 0.000 0.055 0.000 /.../lib/python3.12/site-packages/regex/_main.py:471(complain_unused_args)
200118 0.053 0.000 0.053 0.000 /.../lib/python3.12/enum.py:1129(__new__)
re と regex を比較すると、
- 正規表現のコンパイル: compile_re と compile_regex を比べると、compile_re の方が実行時間が短く、re の方が性能がよいことがわかる。
- 正規表現のマッチング: search_re と search_regex を比べると、search_regex の方が実行時間が短く、regex の方が性能がよいことがわかる。
正規表現のコンパイル回数が多く、マッチングの回数が多くない場合には re がよい。
一方、正規表現のコンパイル回数が少なく、マッチングの回数が多い場合には regex がよい。