2015-01-06 14 views
11

Çok şaşırtıcı sonuçlar elde ettim timeit ile, birisi yanlış bir şey yapsam bana söyleyebilir mi? Python 2.7 kullanıyorum. speedtest.py aitPython zaman çizelgesi ile şaşırtıcı sonuçlar: Counter() vs defaultdict() vs dict()

import random 

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

Bunlar, içeriği:

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

konsol çıktısı:

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

Process finished with exit code 0 

ben

Bu

dosya speedtest_init.py içeriği olan varsayılan olarak düşünün timeit() 1000000 kez kod çalıştırır, bu yüzden zamanları 1000000'e bölmem gerekir, ama şaşırtıcı olan şey nedir? Sayaç, defaultdict() 'den daha yavaştır.

Bu beklenen mi?

DÜZENLEME:

Ayrıca dict kullanarak bir defaultdict (int) daha hızlıdır:

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;') 

bu son sürümü defaultdict (int) daha hızlıdır anlamı daha okunabilirlik umurumda sürece sen defaultdict() yerine dict() kullanmalısınız.

cevap

14

Evet, bu bekleniyor; Counter()yapıcı, self.get() kullanan Counter.update() kullanır ve __missing__ güvencesine dayanmak yerine başlangıç ​​değerlerini yüklemek için kullanılır.

Ayrıca, defaultdict__missing__ fabrika kendisi Counter kaynağı saf Python C'de uygulanan int() gibi bir türünü kullanarak, özellikle Cı-kod tamamen ele alınır ve bu Counter.__missing__ yöntem olarak çalıştırmak için bir Python çerçeve gerektirir.

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

İkisi defaultdict: dict.get() hala C işlenir

Çünkü yapıcı yaklaşım aynı kandırmasına yerel ilk olarak Counter.update() kullanımlarını ve diğer ad self.get arama kullanmanız koşuluyla, bir Counter() için daha hızlı bir yaklaşımdır ve Counter, performansları için değil, işlevsellikleri için oluşturulmuş yardımcı sınıflardır; değil daha hızlı hala olabilir __missing__ kanca güvenerek:

maksimum hız için bir başka ad dict.get() yöntemi kullanarak düzenli bir sözlük var
>>> 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 

. Ancak, Counter veya Counter.most_common() yönteminin çanta davranışını yeniden uygulamak zorunda kalacaksınız. defaultdict kullanım durumları saymanın ötesine geçiyor.

Python 3.2'de, bir Counter() güncelleştirmesi, bu durumu ele alan bir C kitaplığı ekleyerek bir hız artışı elde etti; issue 10667'a bakın. Python'da test 3.4, Counter() Şimdi yapıcı ad verilmiş dict.get durumda yener:

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

Başka bir test ve bir dicti (kullanarak yapmış) bir defaultdict(), soruya – BlueTrin

+0

güncelleyecek Bu gerçekten şaşırtıcı daha hızlıdır; Amaca dayalı bir sınıfın en hızlı uygulama olmasını beklersiniz. Neden daha hızlı olursa, 'Counter' uygulama için 'defaultdict' kullanmıyor? –

+0

@MarkRansom: 'Counter',' defaultdict' işlevinden çok daha fazlasını yapar. Ancak, daha hızlı olan 'defaultdict' alt sınıfı olarak bir' Counter' oluşturabilirsek, belki de bir yama önerebilirsiniz. :-) –