2015-11-10 16 views
11

私は大きな単語リストを管理し、リストの各単語を検証するかどうかを判断するために多くのテストをパスするプロジェクトに取り組んできました。面白いのは、私がitertoolsモジュールのような「高速な」ツールを使用するたびに、それらは遅くなるようです。"any()"がループを使用するよりも遅く動作するのはなぜですか?

最後に、私が何か間違っている可能性があるため、質問をすることにしました。次のコードは、ループの使用に対して関数any()のパフォーマンスをテストしようとします。

#!/usr/bin/python3 
# 

import time 
from unicodedata import normalize 


file_path='./tests' 


start=time.time() 
with open(file_path, encoding='utf-8', mode='rt') as f: 
    tests_list=f.read() 
print('File reading done in {} seconds'.format(time.time() - start)) 

start=time.time() 
tests_list=[line.strip() for line in normalize('NFC',tests_list).splitlines()] 
print('String formalization, and list strip done in {} seconds'.format(time.time()-start)) 
print('{} strings'.format(len(tests_list))) 


unallowed_combinations=['ab','ac','ad','ae','af','ag','ah','ai','af','ax', 
         'ae','rt','rz','bt','du','iz','ip','uy','io','ik', 
         'il','iw','ww','wp'] 


def combination_is_valid(string): 
    if any(combination in string for combination in unallowed_combinations): 
     return False 

    return True 


def combination_is_valid2(string): 
    for combination in unallowed_combinations: 
     if combination in string: 
      return False 

    return True 


print('Testing the performance of any()') 

start=time.time() 
for string in tests_list: 
    combination_is_valid(string) 
print('combination_is_valid ended in {} seconds'.format(time.time()-start)) 


start=time.time() 
for string in tests_list: 
    combination_is_valid2(string) 
print('combination_is_valid2 ended in {} seconds'.format(time.time()-start)) 

以前のコードでは、私が行うテストの種類のかなりの代表であり、我々は結果を見て取る場合:

File reading done in 0.22988605499267578 seconds 
String formalization, and list strip done in 6.803032875061035 seconds 
38709922 strings 
Testing the performance of any() 
combination_is_valid ended in 80.74802565574646 seconds 
combination_is_valid2 ended in 41.69514226913452 seconds 


File reading done in 0.24268722534179688 seconds 
String formalization, and list strip done in 6.720442771911621 seconds 
38709922 strings 
Testing the performance of any() 
combination_is_valid ended in 79.05265760421753 seconds 
combination_is_valid2 ended in 42.24800777435303 seconds 

を私はループを使用すると、使用するよりも半分高速であることをちょっと驚くべき発見しますany()。それについての説明は何でしょうか?私は何か間違っているのですか?

+0

あなたのテストベクトルに 'True'を返す文字列が含まれていますか? –

+0

@ IgnacioVazquez-Abramsはい、真と偽の両方がありますが、私はパーセントをチェックしませんでした。それは非常に興味深いことに、私はそれをスタッフィングすることを考えなかった! – rsm

+1

これはおそらく、ジェネレータの式がループ上で間接的なレベルを提供するため、事態が遅くなるからです。 – interjay

答えて

4

実際any()関数は次の関数に等しいです(私はGNU-Linuxでpython3.4を使用):あなたの第二の機能のようなものですが、

def any(iterable): 
    for element in iterable: 
     if element: 
      return True 
    return False 

any()はブール値を返しますので、値自体をチェックする必要はありません。結果を確認して新しい値を返す必要はありません。したがって、実際に冗長な戻り値とif条件を使用し、別の関数内でanyを呼び出しているため、パフォーマンスの違いがあります。

ここでanyの利点は、あなたのためにすべてのことを行うので、別の関数でラップする必要はないということです。

また、@interjayがコメントに記載したように、私が逃した最も重要な理由は、結果を提供していないany()にジェネレータ式を渡していることと、余分な仕事。

PEP 0289 -- Generator Expressions

に基づいて、ジェネレータ式の意味は、匿名ジェネレータ関数を作成し、それを呼び出すのと同じです。たとえば:あなたはPythonが、それはiter機能と発電機のnextメソッドを呼び出して、次の項目にアクセスするたびに見ることができるように

def __gen(exp): 
    for x in exp: 
     yield x**2 
g = __gen(iter(range(10))) 
print g.next() 

g = (x**2 for x in range(10)) 
print g.next() 

に相当します。そして最後には、そのような場合にはany()を使用するのは難しいことです。

+5

単一の追加の 'if'の効果は、ループに比べて無視できるでしょう。はるかに大きな違いは、 'any'バージョンがジェネレータ式を使用することです。 – interjay

+0

@interjay私はそれが余分な関数呼び出しと条件のためだと言ったので、 'if'のためではありません。しかし、ジェネレータの表現も理由になりますが、それほど多くはありません。 – Kasramvd

+3

ループは、複数の部分文字列を探しています。追加の関数呼び出しと 'if'は時間が2倍になることはほとんどありません。 – interjay

2

あなたの本当の質問が回答されているので、私は暗黙の質問でショットを取るよ:それは重複が含まれているので

あなたは、ちょうどunallowed_combinations = sorted(set(unallowed_combinations))を行うことにより、自由な速度向上を得ることができます。60文字の行の長さを持ついくつかのテストデータ、

combination_is_valid ended in 3.3051061630249023 seconds 
combination_is_valid2 ended in 2.216959238052368 seconds 
combination_is_valid3 ended in 1.4767844676971436 seconds 

のために、私が手にこれを行うの私が知っている最速の方法は、CPythonの3.5で

valid3_re = re.compile("|".join(map(re.escape, unallowed_combinations))) 

def combination_is_valid3(string): 
    return not valid3_re.search(string) 

である、ということを考えると

第三は、正規表現のバージョンで、PyPy3に私は

combination_is_valid ended in 2.2926249504089355 seconds 
combination_is_valid2 ended in 2.0935239791870117 seconds 
combination_is_valid3 ended in 0.14300894737243652 seconds 

FWIWを取得し、これが低い(錆との競争力がありますC++のようなレベルの言語)、実際には正規表現の側で目立って勝ちます。より短い文字列は、オーバーヘッドがより重要であるため、PythonをCPythonよりもはるかに優先します(たとえば、行長10の4倍CPython)。

CPythonの正規表現ランタイムの約1/3しかループオーバーヘッドではないので、私たちはPyPyの正規表現の実装がこのユースケースのために最適化されていると結論づけます。これをPyPyと競合させるCPython正規表現の実装があるかどうかを調べることをお勧めします。

+0

unallowed_combinationsリストの重複した値は、テストの入力時に間違いでしたが、あなたの答えにはとても感謝しています!!私のベンチマークは... '22.354313850402832秒' – rsm

関連する問題