2013-01-21 10 views
8

Oto kod, który napisałem w Pythonie:Python - dołącz VS przedłużyć skuteczność jako

from math import sqrt 
abundant_list = [] 

for i in range(12,28123+1): 
    dividor_list = [1] 
    for j in range(2, int(sqrt(i))+1): 
     if i%j == 0: 
      dividor_list.extend([i/j,j]) 
    if sum(dividor_list) > i: 
     abundant_list.append(i) 

print abundant_list 

Jak widać, kod jest naprawdę stara się być skuteczne, jak to tylko możliwe.

Jest jakakolwiek różnica, jeśli dwukrotnie użyję list.append lub list.extend tylko raz? wiem, może to być drobne różnice, ale naprawdę chciałbym wiedzieć, że :)

+7

Jeśli chcesz wiedzieć, zmierz. – NPE

+1

Byłbym bardzo zaskoczony, gdyby 'extend' nie był szybszy niż 2' .appends' – mgilson

+1

Gdybym miał zoptymalizować to, użyłbym [sito Eratostenesa] (http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes), aby znaleźć liczby pierwsze do 'sqrt (28123)', a następnie dla każdego 'i', skompresowałem je i użyłem [' itertools.product'] (http://docs.python.org/2/library /itertools.html#itertools.product), aby uzyskać wszystkie sposoby łączenia czynników w dzielniki i ostatecznie je podsumować. –

Odpowiedz

16
import timeit 

def append2x(foo): 
    foo.append(1) 
    foo.append(1) 

def extend_lst(foo): 
    foo.extend([1,1]) 

def extend_tup(foo): 
    foo.extend((1,1)) 


l1 = [] 
l2 = [] 
l3 = [] 

print timeit.timeit('append2x(l1)',setup = 'from __main__ import append2x,l1') 
print timeit.timeit('extend_lst(l2)',setup = 'from __main__ import extend_lst,l2') 
print timeit.timeit('extend_tup(l3)',setup = 'from __main__ import extend_tup,l3') 

Oto prosty benchmark. Moje wyniki (OS-X 10.5.8, Core2Duo, FWIW):

0.520906925201 #append 
0.602569103241 #extend-list 
0.357008934021 #extend-tuple 

I to samo uporządkowanie wyników moim Linuksie (Ubuntu, x86-64 Core i7):

0.307395935059 #append 
0.319436073303 #extend-list 
0.238317012787 #extend-tuple 

dla mnie, to mówi, że extend jest szybsza niż append, ale tworząc list jest stosunkowo drogie w porównaniu do tworzenia tuple


EDIT

wskazano w komentarzach poniżej, ze względu na niezmienność krotek, interpreter może zoptymalizować tworzenie krotki out (tworzy krotki raz i ponownie używa go w kółko). W przypadku zmiany kodu do:

def extend_lst(foo): 
    v = 1 
    foo.extend([v,v]) 

def extend_tup(foo): 
    v = 1 
    foo.extend((v,v)) 

Te czasy są praktycznie identyczne:

0.297003984451 #append 
0.344678163528 #extend-list 
0.292304992676 #extend-tuple 

Chociaż tuple nadal konsekwentnie bije wersję listy i ledwo krawędzie na wersję append dla wszystkich prób I zrobili .

Jedną z rzeczy, które zabieram z tego, jest to, że jeśli robisz iterację nad obiektem, który składa się ze wszystkich literałów, wybierz tuple przez list. Jeśli nie składa się wyłącznie z literałów, to naprawdę nie ma znaczenia, czy wybierzesz list lub tuple.

+0

Cóż, właśnie nauczyłem się funkcji "time.clock", i zaskakująco, to pokazuje, że rozszerzenie jest znacznie wolniejsze ... edytuje moją wiadomość – Infinity

+3

Wersja krotki jest szybsza, ponieważ krotka, której używasz jest dosłowna i dlatego ponownie używana (por. kod bajtowy), więc nie trzeba go powtarzać. Użyj zmiennych (np. Przekazuj obiekt do dołączenia do wszystkich funkcji), a różnica zostanie zmniejszona. – delnan

+0

Świetne odpowiedzi chłopaki, dzięki. Zawsze warto otworzyć nowe pytanie, zawsze ucząc się nowych rzeczy :) – Infinity

0

Przyjmują dokładnie tyle samo czasu.

Tu jest czas oczekiwania na kod:

Z dividor_list.extend([i/j,j])

>>> 
0:00:00.410000 
>>> 
0:00:00.383000 
>>> 
0:00:00.389000 

Z dividor_list.append(i/j); dividor_list.append(j)

>>> 
0:00:00.400000 
>>> 
0:00:00.390000 
>>> 
0:00:00.381000 
+4

Musisz podać swój kod czasu, ponieważ łatwo to zepsuć. A ponieważ najwyraźniej nie używałeś 'timeit', musisz usprawiedliwić nie używanie' timeit' ;-) – delnan

+0

Test porównawczy mgilsona sugeruje, że 'dividor_list.extend ((i/j, j))' jest wydajniejszy niż oba, ponieważ nie tworzy pośredniej listy. Tworzenie pośredniej krotki jest tańsze. –

+0

@StevenRumbalski Nie, to pokazuje, że jeden "LOAD_CONST" jest szybszy niż 2 do 3 'LOAD_FAST's, niektóre inne operacje i' BUILD_LIST' ;-) – delnan

9

Warto również zwrócić uwagę, że odpowiedź na to pytanie zależy na mały rozmiar listy/krotki dodawanej w każdej iteracji. W przypadku większych list rozszerzenie jest wyraźnie lepsze (a listy kontra krotki nie mają znaczenia). Zaczynając od mgilson z poziomu , sprawdziłem zachowanie dla kolekcji zawierających 600 elementów, zamiast 2: Przywołanie polecenia 600 razy zajmuje 8 razy więcej czasu niż użycie extend() z ręcznie zdefiniowaną listą/krotką (tj.[v,v,v,v,v,v,v...]):

42.4969689846 
5.45146393776 
5.38034892082 

Większość z tych pięciu sekund jest w rzeczywistości stworzenie lista/krotki. Przygotowanie go przed wywołaniem timeit przynosi razy dla sięgać do

1.42491698265 
0.657584905624 

na listy i krotki, odpowiednio.

Dla bardziej realistyczne (i sprawiedliwszy) przypadku, można dynamicznie generować dane w wywołaniu funkcji:

import timeit 

def append_loop(foo, reps): 
    for i in range(reps): 
     foo.append(i) 

def append_comp(foo, reps): 
    [foo.append(i) for i in range(reps)] 

def extend_lst(foo, reps): 
    foo.extend([i for i in range(reps)]) 

def extend_tup(foo, reps): 
    foo.extend((i for i in range(reps))) 

repetitions = 600 

print timeit.timeit('append_loop([], repetitions)', setup='from __main__ import append_loop, repetitions') 
print timeit.timeit('append_comp([], repetitions)', setup='from __main__ import append_comp, repetitions') 
print timeit.timeit('extend_lst([], repetitions)', setup='from __main__ import extend_lst, repetitions') 
print timeit.timeit('extend_tup([], repetitions)', setup='from __main__ import extend_tup, repetitions') 

(Dołącz jest realizowany zarówno poprzez pętli for i listowego czynnik poza różnice wydajności pomiędzy . dwa sposoby zapętlenie)

taktowanie to:

53.8211231232 
57.1711571217 
19.8829259872 
28.5986201763 

Jak widzimy, przedłużyć z listowego jest wciąż ponad dwa razy szybciej niż dołączanie. Ponadto, zrozumienie krotki wydaje się zauważalnie wolniejsze niż zrozumienie listy, a append_comp wprowadza tylko niepotrzebne narzut na tworzenie list.

+0

Późniejsze ('extend_tup') jest w rzeczywistości genexp, a nie krotką, co wyjaśnia powolność. – yoch

+1

Masz rację co do typu pliku wyjściowego, mój błąd. Jednak po prostu przetestowałem zrozumienie krotki i zapewnia ono taką samą szybkość jak genexp. Zrozumienie listy jest jeszcze szybsze. Oczywiście, jeśli krotka byłaby wcześniejsza, połączenie byłoby szybsze, ale tak samo jest w przypadku wstępnie obliczonej listy. –

+0

Twój benchmark jest stronniczy, ponieważ zawiera czas potrzebny do zbudowania list i krotek. Bez tego rozszerzenie listy o krotkę jest nieco szybsze (przynajmniej dla mnie, używając python 3.5). – yoch