2010年10月24日日曜日

[Python] 例外処理とif...elseのコスト

例えば、あるループ中にあるシーケンスの要素を参照するとき。
for i in range(10):
    value = a[i]
これだと、インデックスiが有効かどうかわからないのでどうにかしたい。

(例1)
とりあえず長さをチェックして参照する。
for i in range(10):
    if i < len(a):
        value = a[i]
これはループで毎回len(a)するのでいかにも遅そう。

(例2)
シーケンスの長さが変わらないのであればループの前にあらかじめ長さをとっておこう。
alen = len(a)
for i in range(10):
    if i < alen:
        value = a[i]

(例3)
とりあえず参照して例外を拾おう。
for i in range(10):
    try:
        value = a[i]
    except IndexError:
        pass

どれを使うのがよさげなのか計ってみよう。

テスト用スクリプト
#!/usr/bin/env python
import timeit

# インデックスiが範囲内の場合のセットアップ
setup1 = """
a = [ 1, 2, 3 ]
alen = len(a)
i = 1
"""

# インデックスiが範囲外の場合のセットアップ
setup2 = """
a = [ 1, 2, 3 ]
alen = len(a)
i = 5
"""

# 例1用ステートメント
stmt1 = """
if i < len(a):
    a[i]
else:
    pass
"""

# 例2用ステートメント
stmt2 = """
if i < alen:
    a[i]
else:
    pass
"""

# 例3用ステートメント
stmt3 = """
try:
    a[i]
except IndexError:
    pass
"""

print timeit.Timer(setup = setup1, stmt = stmt1).timeit()
print timeit.Timer(setup = setup1, stmt = stmt2).timeit()
print timeit.Timer(setup = setup1, stmt = stmt3).timeit()
print timeit.Timer(setup = setup2, stmt = stmt1).timeit()
print timeit.Timer(setup = setup2, stmt = stmt2).timeit()
print timeit.Timer(setup = setup2, stmt = stmt3).timeit()
実行結果
# インデックスが範囲内
0.361014127731 # 例1
0.125293016434 # 例2
0.128052949905 # 例3
# インデックスが範囲外
0.284233808517 # 例1
0.101886034012 # 例2
1.82788801193  # 例3

例外処理で実装した場合(例3)のインデックスが範囲外になる時が飛びぬけて遅い。
つまり、例外発生のコストは結構大きい。
例外発生が頻繁に発生することが予測される場合、if...elseで簡単に
代替できるのであればif...elseを使ったほうが高速。

0 件のコメント:

コメントを投稿