Spodziewałem array.array
się, że będę szybszy niż listy, ponieważ tablice wydają się być rozpakowane.
Jednak otrzymuję następujący wynik:
In [1]: import array
In [2]: L = list(range(100000000))
In [3]: A = array.array('l', range(100000000))
In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop
In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop
In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop
In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop
Co może być przyczyną takiej różnicy?
python
arrays
performance
boxing
python-internals
Valentin Lorentz
źródło
źródło
array
pakietu. Jeśli chcesz robić duże ilości matematyki, Numpy działa z prędkością światła (tj. C) i zwykle lepiej niż naiwne implementacje rzeczy takich jaksum()
).array
że konwersja ciągu liczb całkowitych (reprezentujących bajty ASCII) nastr
obiekt jest dość szybka . Sam Guido wymyślił to dopiero po wielu innych rozwiązaniach i był dość zaskoczony występem. W każdym razie jest to jedyne miejsce, w którym pamiętam, że jest przydatne.numpy
znacznie lepiej radzi sobie z tablicami, ale jest zależnością od strony trzeciej.Odpowiedzi:
Przechowywanie jest „rozpakowanych”, ale za każdym razem uzyskać dostęp do elementu Python ma do „skrzynki” to (umieścić go w sposób regularny obiekt Pythona), aby zrobić coś z nim. Na przykład, twoja
sum(A)
iteracja po tablicy i umieszcza każdą liczbę całkowitą, pojedynczo, w zwykłymint
obiekcie Pythona . To kosztuje czas. W twoim przypadkusum(L)
, wszystko zostało wykonane w momencie tworzenia listy.Ostatecznie macierz jest generalnie wolniejsza, ale wymaga znacznie mniej pamięci.
Oto odpowiedni kod z najnowszej wersji Pythona 3, ale te same podstawowe pomysły dotyczą wszystkich implementacji CPythona od czasu pierwszego wydania Pythona.
Oto kod dostępu do elementu listy:
Jest w tym bardzo mało:
somelist[i]
po prostu zwracai
'-ty obiekt na liście (a wszystkie obiekty Pythona w CPythonie są wskaźnikami do struktury, której początkowy segment jest zgodny z układem astruct PyObject
).A oto
__getitem__
implementacja dlaarray
z kodem typul
:Pamięć surowa jest traktowana jako wektor
C
long
liczb całkowitych natywnych dla platformy ;i
„thC long
jest odczytywany w górę; a następniePyLong_FromLong()
jest wywoływana w celu zawijania („box”) natywnego obiektuC long
w Pythonielong
(który w Pythonie 3, który eliminuje rozróżnienie międzyint
i w Pythonie 2long
, jest w rzeczywistości wyświetlany jako typint
).To opakowanie musi przydzielić nową pamięć dla
int
obiektu Pythona i rozpylićC long
do niego bity natywnego . W kontekście oryginalnego przykładu, okres istnienia tego obiektu jest bardzo krótki (wystarczy,sum()
aby dodać zawartość do bieżącej sumy), a następnie zwolnienie nowegoint
obiektu wymaga więcej czasu .To stąd bierze się różnica prędkości, zawsze pochodziła i zawsze będzie pochodzić w implementacji CPythona.
źródło
Aby dodać do doskonałej odpowiedzi Tima Petersa, tablice implementują protokół bufora , podczas gdy listy nie. Oznacza to, że jeśli piszesz rozszerzenie C (lub moralny odpowiednik, taki jak pisanie modułu Cython ), możesz uzyskać dostęp do elementów tablicy i pracować z nimi znacznie szybciej niż cokolwiek Python może zrobić. Zapewni to znaczną poprawę szybkości, prawdopodobnie znacznie przekraczającą rząd wielkości. Ma jednak kilka wad:
Idąc prosto do rozszerzeń C, możesz użyć młota kowalskiego do uderzenia muchy, w zależności od przypadku użycia. Najpierw powinieneś zbadać NumPy i sprawdzić, czy jest wystarczająco potężny, aby wykonać dowolną matematykę, którą próbujesz zrobić. Będzie również znacznie szybszy niż natywny Python, jeśli będzie używany poprawnie.
źródło
Tim Peters odpowiedział, dlaczego jest to powolne, ale zobaczmy, jak to poprawić .
Trzymając się twojego przykładu
sum(range(...))
(współczynnik 10 mniejszy niż twój przykład, aby zmieścić się w pamięci tutaj):W ten sposób numpy musi również box / unbox, co ma dodatkowy narzut. Aby było to szybkie, należy trzymać się kodu numpy c:
Więc od rozwiązania listy do wersji numpy jest to współczynnik 16 w czasie wykonywania.
Sprawdźmy też, jak długo trwa tworzenie tych struktur danych
Zdecydowany zwycięzca: Numpy
Należy również pamiętać, że tworzenie struktury danych zajmuje mniej więcej tyle samo czasu, co sumowanie, jeśli nie więcej. Przydzielanie pamięci jest powolne.
Wykorzystanie pamięci tych:
Więc zajmują one 8 bajtów na liczbę z różnym narzutem. Dla zakresu, którego używamy, wystarczą liczby 32-bitowe, więc możemy zaoszczędzić trochę pamięci.
Okazuje się jednak, że dodanie 64-bitowych intów jest szybsze niż 32-bitowe int na moim komputerze, więc jest to tego warte tylko wtedy, gdy jesteś ograniczony pamięcią / przepustowością.
źródło
proszę zauważyć, że
100000000
oznacza to, że10^8
nie należy10^7
, a moje wyniki są następujące:źródło