Podczas próby odpowiedzi na pytanie What is the preferred way to compose a set from multiple lists in Python wykonałem analizę wydajności i doszedłem do wniosku z nieco zaskakującym wnioskiem.Dlaczego tworzenie zestawu z połączonej listy jest szybsze niż przy użyciu polecenia `.update`?
Korzystanie
python -m timeit -s '
import itertools
import random
n=1000000
random.seed(0)
A = [random.randrange(1<<30) for _ in xrange(n)]
B = [random.randrange(1<<30) for _ in xrange(n)]
C = [random.randrange(1<<30) for _ in xrange(n)]'
dla konfiguracji I timed następujące fragmenty:
> $TIMEIT 'set(A+B+C)'
10 loops, best of 3: 872 msec per loop
> $TIMEIT 's = set(A); s.update(B); s.update(C)'
10 loops, best of 3: 930 msec per loop
> $TIMEIT 's = set(itertools.chain(A,B,C))'
10 loops, best of 3: 941 msec per loop
ku mojemu zaskoczeniu, set(A+B+C)
jest najszybciej pomimo faktu, że tworzy listę pośredni zawierający 3000000 elementy . .update
i itertools.chain
są wolniejsze, mimo że żadne z nich nie kopiuje żadnych list.
Co tu się dzieje?
EDIT: Na drugim komputerze (OS X 10.10.5, Python 2.7.10, 2.5GHz Core i7), wpadłem następujący skrypt (który uruchamia testy przodu i do tyłu, aby uniknąć efektów zamawiania):
SETUP='import itertools
import random
n=1000000
random.seed(0)
A = [random.randrange(1<<30) for _ in xrange(n)]
B = [random.randrange(1<<30) for _ in xrange(n)]
C = [random.randrange(1<<30) for _ in xrange(n)]'
python -m timeit -s "$SETUP" 'set(A+B+C)'
python -m timeit -s "$SETUP" 's = set(A); s.update(B); s.update(C)'
python -m timeit -s "$SETUP" 's = set(itertools.chain(A,B,C))'
python -m timeit -s "$SETUP" 's = set(itertools.chain(A,B,C))'
python -m timeit -s "$SETUP" 's = set(A); s.update(B); s.update(C)'
python -m timeit -s "$SETUP" 'set(A+B+C)'
i uzyskał następujące wyniki:
10 loops, best of 3: 579 msec per loop
10 loops, best of 3: 726 msec per loop
10 loops, best of 3: 775 msec per loop
10 loops, best of 3: 761 msec per loop
10 loops, best of 3: 737 msec per loop
10 loops, best of 3: 555 msec per loop
teraz set(A+B+C)
jest wyraźnie szybciej, a wyniki są dość stabl e - trudno jest przypisać to do zwykłego błędu pomiaru. Wielokrotne uruchamianie tego skryptu daje podobne wyniki.
Jedyne przypuszczenie mogę zrobić jest to pierwszy przypadek przechodzi lista, która ma znaną długość, a więc być może konstrukcja zestawu może rozsądniej wybrać początkowe wymaganie pamięci podstawowej, w przeciwieństwie do dwóch pozostałych, gdzie zestaw jest tworzony i dwukrotnie skalowany (drugi przypadek) lub tworzony za pomocą iteratora, w którym potencjalnie zmienia rozmiar pochować wiele razy. –
Jeśli nie zmienili 'set_init', to nie jest tak, jak się wydaje. ['set_init'] (http://svn.python.org/projects/python/trunk/Objects/setobject.c) po prostu dzwoni prosto do' set_update_internal', który właśnie pętli nad elementami. (Pobieram z 'hg.python.org' ale ten serwer wydaje się w tej chwili niedostępny) – nneonneo
powiązane: [Łączenie dwóch posortowanych list w Pythonie] (http://stackoverflow.com/a/482848/4279) – jfs