試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Pythonの正規表現モジュールにおけるコンパイルの効果を検証

Pythonで正規表現による文字列処理(検索・置換・分割)を行う」の中で、「同じパターンを複数回使用する状況下では、正規表現コンパイルを行って正規表現オブジェクトを作成して使い回したほうが、毎回関数を呼ぶやり方より処理時間が短縮される」ということに触れた。
ここでは、実際にどれほどの違いが出るのかを実験してみることにする。
[任意]ファイル名: recachetest.py

#! /usr/bin/python
# -*- encoding: utf-8 -*-

from optparse import OptionParser
import time
import re

def search_nocompile(str, pattstr, cnt):
  """
  文字列strをパターンpattstrでcnt回検索(検索を行うだけ)
  """
  for x in range(cnt):
    re.search(pattstr, str)  # パターンを毎回コンパイルして使い捨てている
def search_compile(str, pattobj, cnt):
  """
  文字列strを(コンパイルされた)正規表現オブジェクトpattobjでcnt回検索
  """
  for x in range(cnt):
    pattobj.search(str)      # コンパイルされたものを使い回している
def main():
  """
  メイン処理
  """
  parser_defaults = { "string"       : "test string [http://d.hatena.ne.jp/kakurasan/:title=link]",
                      "pattern"      : "\[(http://.+?):title=(.+?)\]",
                      "count"        : 1000000,
                      "compile"      : False, }
  parser = OptionParser()
  parser.set_defaults(**parser_defaults)
  parser.add_option("-s", "--string",
                    dest="string",
                    action="store",
                    type="string",
                    help=u"文字列STRINGの繰り返しを使用します".encode("utf-8"),
                    metavar="STRING")
  parser.add_option("-p", "--pattern",
                    dest="pattern",
                    action="store",
                    type="string",
                    help=u"検索するパターンを指定します".encode("utf-8"),
                    metavar="PATTERN")
  parser.add_option("-t", "--count",
                    dest="count",
                    action="store",
                    type="int",
                    help=u"COUNT回のマッチを繰り返し行います".encode("utf-8"),
                    metavar="COUNT")
  parser.add_option("-c", "--compile",
                    dest="compile",
                    action="store_true",
                    help=u"コンパイルを行います".encode("utf-8"))
  (options, args) = parser.parse_args()

  time_before = int(time.time())  # 開始前の時刻
  if options.compile == True:
    print u"コンパイルを行います".encode("utf-8")
    patt = re.compile(options.pattern)
    search_compile(options.string, patt, options.count)
  else:
    print u"コンパイルを行いません".encode("utf-8")
    search_nocompile(options.string, options.pattern, options.count)
  time_after = int(time.time())   # 終了時の時刻
  print u"所要時間: 約 %d 秒".encode("utf-8") % (time_after - time_before)

if __name__ == "__main__":
  main()

処理としては、コンパイルをして使い回す場合と使い捨てる場合のそれぞれで、同じ文字列・同じパターンを使用して検索処理を繰り返しているだけ。
下は実行例。オプション指定により、ある程度の調整ができるようにしてある。

$ ./recachetest.py -h
usage: recachetest.py [options]

options:
  -h, --help            show this help message and exit
  -s STRING, --string=STRING
                        文字列STRINGの繰り返しを使用します
  -p PATTERN, --pattern=PATTERN
                        検索するパターンを指定します
  -t COUNT, --count=COUNT
                        COUNT回のマッチを繰り返し行います
  -c, --compile         コンパイルを行います
$ ./recachetest.py
コンパイルを行いません
所要時間: 約 14 秒
$ ./recachetest.py -c
コンパイルを行います
所要時間: 約 8 秒