文字列置換のベンチマーク

Python で文章中に含まれる半角記号(;/?:@&+,()[]!"#%'~\`)と半角スペースを全て特定の値(-)に置換したい。


例えば、「エメラルド Tour 2010(初回限定盤) [DVD] 」は「エメラルド-Tour-2010-初回限定盤---DVD-」といった感じ。
str.replace をするのか、re.sub が良いのかと思い Twitter で呟いたら以下のリプライを頂いた。

ということで以下のようなのを書いた。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
from benchmarker import Benchmarker, cmdopt
def replace_reserved_chars(value):
    ret = value.replace(';', '-') \
               .replace('/', '-') \
               .replace('?', '-') \
               .replace(':', '-') \
               .replace('@', '-') \
               .replace('&', '-') \
               .replace('=', '-') \
               .replace('+', '-') \
               .replace(',', '-') \
               .replace('(', '-') \
               .replace(')', '-') \
               .replace('[', '-') \
               .replace(']', '-') \
               .replace('!', '-') \
               .replace('"', '-') \
               .replace('#', '-') \
               .replace('%', '-') \
               .replace("'", '-') \
               .replace('~', '-') \
               .replace('\\', '-') \
               .replace('`', '-') \
               .replace(' ', '-')

    return ret
def replace_reserved_chars_regex(value):
    pattern = r'[\s+]|;|/|\?|:|@|&|=|\+|,|\(|\)|\[|\]|!|"|#|%|\'|~|\\|`'
    ret = re.sub(pattern, '-', value)
    return ret

def replace_reserved_chars_regex_compile(value):
    pattern = r'[\s+]|;|/|\?|:|@|&|=|\+|,|\(|\)|\[|\]|!|"|#|%|\'|~|\\|`'
    r = re.compile(pattern)
    ret = r.sub('-', value)
    return ret

cmdopt.parse()

with Benchmarker(width=20, loop=100*1000) as bm:
    for _ in bm.empty():
        pass
    for _ in bm('replace one'):
        replace_reserved_chars('Hello world!')
    for _ in bm('regex one'):
        replace_reserved_chars_regex('Hello world!')
    for _ in bm('regex compile one'):
        replace_reserved_chars_regex_compile('Hello world!')

with Benchmarker(width=20, loop=100*1000) as bm:
    for _ in bm('replace all'):
        replace_reserved_chars(' ;/?:@&+,()[]!"#%\'~\\`')
    for _ in bm('regex all'):
        replace_reserved_chars_regex(' ;/?:@&+,()[]!"#%\'~\\`')
    for _ in bm('regex compile all'):
        replace_reserved_chars_regex_compile(' ;/?:@&+,()[]!"#%\'~\\`')

with Benchmarker(width=20, loop=100*1000) as bm:
    for _ in bm('replace'):
        replace_reserved_chars(u'エメラルド Tour 2010(初回限定盤) [DVD]')
    for _ in bm('regex'):
        replace_reserved_chars_regex(u'エメラルド Tour 2010(初回限定盤) [DVD]')
    for _ in bm('regex compile'):
        replace_reserved_chars_regex_compile(u'エメラルド Tour 2010(初回限定盤) [DVD]')

with Benchmarker(width=20, loop=100*1000) as bm:
    for _ in bm('replace all'):
        replace_reserved_chars(u'エメラルド-Tour-2010-初回限定盤-DVD')
    for _ in bm('regex all'):
        replace_reserved_chars_regex(u'エメラルド-Tour-2010-初回限定盤-DVD')
    for _ in bm('regex compile all'):
        replace_reserved_chars_regex_compile(u'エメラルド-Tour-2010-初回限定盤-DVD')

ベンチマークには、Benchmarker を使用。


結果
文字列中に記号が一つだけ。

## benchmarker:       release 3.0.1 (for python)
## python platform:   darwin [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]
## python version:    2.6.6
## python executable: /opt/local/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python

##                       user       sys     total      real
(Empty)                0.0100    0.0000    0.0100    0.0137
replace one            0.8600    0.0000    0.8600    0.8775
re one                 0.6000    0.0100    0.6100    0.6281
re compile one         0.5900    0.0000    0.5900    0.5970

## Ranking               real
re compile one         0.5970 (100.0%) *************************
re one                 0.6281 ( 95.0%) ************************
replace one            0.8775 ( 68.0%) *****************

## Ratio Matrix          real    [01]    [02]    [03]
[01] re compile one    0.5970  100.0%  105.2%  147.0%
[02] re one            0.6281   95.0%  100.0%  139.7%
[03] replace one       0.8775   68.0%   71.6%  100.0%

文中に少し含んでいる場合。

##                       user       sys     total      real
(Empty)                0.0100    0.0000    0.0100    0.0147
replace                1.2200    0.0100    1.2300    1.2485
re                     1.2600    0.0000    1.2600    1.2723
re compile             1.2500    0.0100    1.2600    1.2768

## Ranking               real
replace                1.2485 (100.0%) *************************
re                     1.2723 ( 98.1%) *************************
re compile             1.2768 ( 97.8%) ************************

## Ratio Matrix          real    [01]    [02]    [03]
[01] replace           1.2485  100.0%  101.9%  102.3%
[02] re                1.2723   98.1%  100.0%  100.3%
[03] re compile        1.2768   97.8%   99.7%  100.0%

文章が全て置換対象文字だけの場合。

##                       user       sys     total      real
(Empty)                0.0200    0.0000    0.0200    0.0139
replace all            1.0100    0.0000    1.0100    1.0289
re all                 1.1600    0.0100    1.1700    1.2312
re compile all         1.1400    0.0000    1.1400    1.1731

## Ranking               real
replace all            1.0289 (100.0%) *************************
re compile all         1.1731 ( 87.7%) **********************
re all                 1.2312 ( 83.6%) *********************

## Ratio Matrix          real    [01]    [02]    [03]
[01] replace all       1.0289  100.0%  114.0%  119.7%
[02] re compile all    1.1731   87.7%  100.0%  104.9%
[03] re all            1.2312   83.6%   95.3%  100.0%

置換対象が全くない場合。

##                       user       sys     total      real
(Empty)                0.0100    0.0000    0.0100    0.0134
replace none           1.1300    0.0000    1.1300    1.1367
re none                0.7700    0.0000    0.7700    0.7902
re compile none        0.7600    0.0100    0.7700    0.7759

## Ranking               real
re compile none        0.7759 (100.0%) *************************
re none                0.7902 ( 98.2%) *************************
replace none           1.1367 ( 68.3%) *****************

## Ratio Matrix          real    [01]    [02]    [03]
[01] re compile none   0.7759  100.0%  101.8%  146.5%
[02] re none           0.7902   98.2%  100.0%  143.9%
[03] replace none      1.1367   68.3%   69.5%  100.0%

記号が 1 つの場合は、re モジュールの方が速い。
記号が少しの場合は、replace の方が速い(ただし他のとは誤差レベル)。
全て記号の場合は replace の方が速い。
全く含まない場合は、re モジュールの方が速い。


正直誤差のような気がする…。
現実的には記号が少しの場合と、全くない場合というユースケースが大半を締めそう。
なので、re.compile を使ってみよう。