2015-01-06 4 views
11

나는 아주 이상한 결과를 timeit에서 얻었습니다. 누군가 내가 잘못하고 있는지 말해 줄 수 있습니까? 파이썬 2.7을 사용하고 있습니다. speedtest.py의파이썬 timeit에 대한 놀랄만 한 결과 : counter() 대 defaultdict() vs dict()

import random 

to_count = [random.randint(0, 100) for r in range(60)] 

이가되는 내용 :

__author__ = 'BlueTrin' 

import timeit 

def test_init1(): 
    print(timeit.timeit('import speedtest_init')) 

def test_counter1(): 
    s = """\ 
    d = defaultdict(int); 
    for i in speedtest_init.to_count: 
     d[i] += 1 
    """ 
    print(timeit.timeit(s, 'from collections import defaultdict; import speedtest_init;')) 

def test_counter2(): 
    print(timeit.timeit('d = Counter(speedtest_init.to_count);', 'from collections import Counter; import speedtest_init;')) 


if __name__ == "__main__": 
    test_init1() 
    test_counter1() 
    test_counter2() 

콘솔 출력은 다음과 같습니다

C:\Python27\python.exe C:/Dev/codility/chlorum2014/speedtest.py 
2.71501962931 
65.7090444503 
91.2953839048 

Process finished with exit code 0 

I

파일 speedtest_init.py의 내용입니다 기본적으로 timeit()이 코드를 1000000 번 실행하므로 시간을 1000000으로 나눌 필요가 있다고 생각하지만 놀라운 것은 무엇입니까? 카운터가 defaultdict()보다 느리다는 것입니다.

예상 되나요?

편집 :

또한 딕셔너리를 사용하는 것은 defaultdict (INT)보다 빠르다 :

def test_counter3(): 
    s = """\ 
    d = {}; 
    for i in speedtest_init.to_count: 
     if i not in d: 
      d[i] = 1 
     else: 
      d[i] += 1 
    """ 
    print(timeit.timeit(stmt=s, setup='from collections import defaultdict; import speedtest_init;') 

이 마지막 버전은 defaultdict (INT)보다 빠른 의미를 좀 더 가독성에 대한 관심을하지 않는 한 당신 defaultdict() 대신 dict()를 사용해야합니다.

답변

14

예, 예상됩니다. Counter()생성자__missing__에 의존하지 않고 self.get()을 사용하여 초기 값을로드하는 Counter.update()을 사용합니다.

또한 defaultdict__missing__ 공장 자체 Counter 소스 순수한 파이썬이다 C. 구현된다 int() 같은 다른 형태를 사용하여, 특히, C 코드 전적으로 취급 등 Counter.__missing__ 방법으로 실행할 파이썬 프레임을 필요로한다.

>>> import timeit 
>>> import random 
>>> to_count = [random.randint(0, 100) for r in range(60)] 
>>> timeit.timeit('for i in to_count: c[i] += 1', 
...    'from collections import Counter; from __main__ import to_count; c = Counter()', 
...    number=10000) 
0.2510359287261963 
>>> timeit.timeit('for i in to_count: c[i] = c_get(i, 0) + 1', 
...    'from collections import Counter; from __main__ import to_count; c = Counter(); c_get = c.get', 
...    number=10000) 
0.20978617668151855 

모두 defaultdict : dict.get() 여전히 C에서 처리됩니다

때문에, 생성자의 접근 방식은 같은 트릭을 로컬 첫째로 Counter.update() 사용과 별명 self.get 조회를 사용하여 제공하는 Counter()에 대한 빠른 접근 방법이다 및 Counter은 성능이 아닌 기능을 위해 만들어진 유용한 클래스입니다. 빠르지 계속 될 수 __missing__ 훅에 의존 :

최대 속도에 대한 별칭 dict.get() 방법을 사용하여 일반 사전의
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1', 
...    'from __main__ import to_count; d = {}; d_get = d.get', 
...    number=10000) 
0.11437392234802246 

. 그렇지만 Counter 또는 Counter.most_common() 메소드의 가방 동작을 다시 구현해야합니다. defaultdict 유스 케이스는 계산을 넘어서게됩니다.

파이썬 3.2에서는 Counter()을 업데이트 할 때이 케이스를 처리하는 C 라이브러리를 추가하여 속도를 향상 시켰습니다. issue 10667을 참조하십시오. Python 3에서 테스트하기.4는 Counter() 생성자는 이제 별칭 dict.get 경우 친다 :

>>> timeit.timeit('Counter(to_count)', 
...    'from collections import Counter; from __main__ import to_count', 
...    number=100000) 
0.8332311600097455 
>>> timeit.timeit('for i in to_count: d[i] = d_get(i, 0) + 1', 
...    'from __main__ import to_count; d = {}; d_get = d.get', 
...    number=100000) 
0.961191965994658 
+0

내가 다른 테스트와 딕셔너리를 (사용했을)를 defaultdict(), 질문 – BlueTrin

+0

업데이트됩니다 이것은 참으로 놀라운보다 빠릅니다을; 특수 목적의 클래스를 가장 빠른 구현으로 기대할 수 있습니다. 더 빠른 경우'Counter'가 구현에'defaultdict'를 사용하지 않는 이유는 무엇입니까? –

+0

@MarkRansom :'Counter'는'defaultdict'보다 훨씬 많은 일을합니다. 하지만 더 빠른'defaultdict' 서브 클래스로서'Counter'를 생성 할 수 있다면 아마도 패치를 제안 할 수 있습니다. :-) –