2015-01-06 10 views
11

Otrzymałem bardzo zaskakujące wyniki z timeit, czy ktoś może mi powiedzieć, czy robię coś nie tak? Używam Pythona 2.7.Zaskakujące wyniki dzięki Pythonowi timeit: Counter() vs defaultdict() vs dict()

To jest zawartość pliku speedtest_init.py:

import random 

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

Są zawartość speedtest.py:

__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() 

Wyjście konsoli:

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

Process finished with exit code 0 

I domyślnie timeit() uruchamia 1000000 razy kod, więc muszę podzielić czasy na 1000000, ale co jest zaskakujące jest to, że licznik jest wolniejszy niż defaultdict().

Czy to jest oczekiwane?

EDIT:

także przy użyciu dict jest szybszy niż 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;') 

ta ostatnia wersja jest szybsza niż defaultdict (int), co oznacza, że ​​jeśli nie bardziej dbać o czytelność ty powinien użyć funkcji dict() zamiast defaultdict().

Odpowiedz

14

Tak, to jest oczekiwane; konstruktor konstruuje się, aby używać , który wykorzystuje self.get() do ładowania wartości początkowych zamiast polegania na __missing__.

Ponadto fabrycznie defaultdict__missing__ są obsługiwane całkowicie kodem C, zwłaszcza przy użyciu innego rodzaju jak int() który sam jest realizowany w C. źródło Counter jest Pythonie i jako taki sposób wymaga, aby ramka Counter.__missing__ Pythona do wykonania.

Ponieważ dict.get() jest nadal obsługiwane w języku C, podejście konstruktor jest szybsze podejście do Counter(), pod warunkiem korzystania z tej samej sztuczki Counter.update() zastosowań i alias odnośnika self.get jako lokalny pierwsze:

>>> 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 

Zarówno defaultdict i Counter są pomocne klasy zbudowane dla ich funkcjonalności, a nie ich wydajności; Nie opierając się na haku __missing__ może być jeszcze szybciej:

>>> 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 

To regularne słownika za pomocą aliasem dict.get() metodę maksymalnej prędkości. Ale wtedy będziesz również musiał ponownie zaimplementować zachowanie worka Counter lub metody Counter.most_common(). Przypadki użycia są znacznie większe niż liczenie.

W Pythonie 3.2, aktualizacja Counter() uzyskała zwiększenie prędkości przez dodanie biblioteki C, która obsługuje ten przypadek; patrz issue 10667. Testowanie w Pythonie 3.4, konstruktor Counter() teraz bije aliasem dict.get sprawy:

>>> 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

zrobiłem kolejny test i przy użyciu dict() jest szybsza niż defaultdict(), zaktualizuje pytanie – BlueTrin

+0

To naprawdę zaskakujące; można oczekiwać, że klasa celowa będzie najszybszą implementacją. Dlaczego 'Counter' nie używa' defaultdict' do swojej implementacji, jeśli jest szybszy? –

+0

@MarkRansom: 'Counter' robi znacznie więcej niż' defaultdict'. Ale jeśli możemy utworzyć 'Counter' jako podklasę' defaultdict', która jest szybsza, być może możesz zaproponować łatkę. :-) –

Powiązane problemy