2014-06-23 16 views
7

Zasadniczo potrzebuję utworzyć tabelę odnośników z nieregularnymi identyfikatorami całkowitymi. Zastanawiam się, czy pod względem prędkości wyszukiwania, ogólnie lepiej jest używać dict z kluczami całkowitymi, tak czy inaczej, z bardzo długim list z dużą ilością pustych indeksów. Wydaje mi się, że list może być jeszcze szybszy, ponieważ Python powinien dokładnie wiedzieć, gdzie szukać, ale zastanawiam się, czy istnieją jakiekolwiek procesy zaplecza z dict, które mają zrekompensować i czy dodatkowe wymagania dotyczące pamięci dla pustych gniazd list będą negować (Prawdopodobnie) łatwiej osiąga zyski prędkości. Czy są jakieś alternatywy dla list s i dict s, które mogą być lepiej dostosowane do tego?Lepiej używać dyktafonu klawiszy całkowitych lub bardzo długiej listy?

Widziałem to pytanie, ale nie całkowicie odpowiedzieć na moje: Dictionary access speed comparison with integer key against string key

ETA: Jestem realizacji tablic przeglądowych tak dwa razy w moim programie. Jedna instancja widzi maksymalny identyfikator 5000 z zapełnionymi 70-100 obiektami; drugi ma max id 750 z 20-30 zaludnionych.

+10

O ile nie jest to kod niezwykle wrażliwy na wydajność, nie martwiłbym się. Użyj "dyktatu" - jest semantycznie dużo bliżej tego, co próbujesz osiągnąć. – sapi

Odpowiedz

8

Aby odpowiedzieć na pytanie o dict vs list trzeba by dać dokładnie informacje o liczbie elementów, liczba brakujących elementów etc, więc tha możemy oszacować dokładnie zużycie pamięci struktury dwóch danych i spróbuj przewidzieć i/lub sprawdzić swoją wydajność.

Na ogół dict z N par klucz-wartość wymaga nieco więcej pamięci niż list z N wartości:

  • dict musi śledzić klawiszy
  • dict jest nie więcej niż 2/3 pełne. Gdy tak się stanie, przydzielona pamięć jest podwojona (wymagane jest to, aby O (1) amortyzował operacje czasowe na dict).

Jednak istnieje alternatywa do tych struktur danych, które powinny zapewnić bardzo dobrą wydajność: blist. Pakiet blist zapewnia interfejs, który odpowiada interfejsowi list, ale jest zaimplementowany przy użyciu B-drzew. Potrafi wydajnie obsługiwać rzadkie listy. Większość operacji ma czas O(1) lub O(log n), więc są dość wydajne.

Na przykład można najpierw utworzyć rzadki blist robi:

from blist import blist 

seq = blist([None]) 
seq *= 2**30 # create a 2**30 element blist. Instantaneous! 

A potem można ustawić tylko indeksy, które mają wartość:

for i, value in zip(indices, values): 
    seq[i] = value 

Pełna dokumentacja jest here.

Należy zauważyć, że blist też inne wydajne operacji, takich jak:

  • Połączenie dwóch blist e się O(log n) czas
  • Biorąc [i:j] segment zajmuje O(log n) czasu
  • Wkładanie elementu w danej indeksu, O(log n) operacji
  • Popping element (z każdej pozycji) zajmuje O(log n) operacji

Skoro dał kilka numerów, oto jak to porównać do dict s:

>>> from blist import blist 
>>> b = blist([None]) 
>>> b *= 5000 
>>> for i in range(100):b[i] = i 
... 
>>> b.__sizeof__() 
2660 
>>> d = dict() 
>>> for i in range(100):d[i] = i 
... 
>>> d.__sizeof__() 
6216 
>>> b = blist([None]) 
>>> b *= 750 
>>> for i in range(30):b[i] = i 
... 
>>> b.__sizeof__() 
1580 
>>> d = dict() 
>>> for i in range(30):d[i] = i 
... 
>>> d.__sizeof__() 
1608 

W obu przypadkach blist zajmuje mniej pamięci (w pierwszym przypadku potrzebny 1/3 pamięci równoważnego dict). Należy pamiętać, że pamięć podjęta przez blist zależy również od tego, czy indeksy są sąsiadujące (sąsiadujące jest lepsze). Jednak nawet przy użyciu indeksów losowych:

>>> b = blist([None]) 
>>> b *= 5000 
>>> import random 
>>> for i in range(100):b[random.randint(0, 4999)] = i 
... 
>>> b.__sizeof__() 
2916 

To wciąż znacznie lepsze niż dict.

Nawet lookup czasy są lepsze:

In [1]: from blist import blist 
    ...: import random 
    ...: 

In [2]: b = blist([None]) 

In [3]: b *= 5000 

In [4]: for i in range(100):b[random.randint(0, 4999)] = i 

In [5]: %timeit b[0] 
10000000 loops, best of 3: 50.7 ns per loop 

In [6]: d = dict() 

In [7]: for i in range(100):d[random.randint(0, 4999)] = i 

In [10]: %timeit d[1024] # 1024 is an existing key in this dictionary 
10000000 loops, best of 3: 70.7 ns per loop 

In [11]: %timeit b[1024] 
10000000 loops, best of 3: 50.7 ns per loop 

Uwaga że list trwa około 47 ns do wyszukiwania indeksu na moim komputerze, więc blist jest naprawdę bardzo blisko list pod względem wydajności przeglądowej na małych list jak co masz.

+0

Tak więc 'blist' wydaje się być odpowiedzią na stwierdzenie @begueradj, że jest szybszy dostęp do' dict' niż 'list'. W jaki sposób wykorzystanie pamięci rzadkiej 'blist' porównuje się do' dict'? Wygląda na to, że 95% jest puste (z numerów dodanych do pytania) 'blist' nadal będzie zużywać więcej pamięci niż' dict' (chociaż nie rozumiem, jak działa "_____ kiedykolwiek więcej niż 2/3 pełne"; "Dict" po prostu zajmuje 1,5x rozmiaru swoich danych?), a nawet więcej pamięci, jeśli zaczynam od ślepego robienia dużego 'blista 'zamiast obliczania max idu w pierwszej kolejności, czy też' blist' jakoś zwinąć puste wskaźniki? – Rus925

+1

@ Rus925 Rzadka blista używa 'O (log n)' pamięci gdzie 'n' będzie długością rzeczywistej listy. Oznacza to, że rzadka blista '2 ** 30' (jak w przykładzie) zajmuje około' k * 30' bajtów, gdzie 'k' jest trochę stała. Wykorzystywana pamięć zależy od tego, jak rzadka jest lista i gdzie znajdują się te elementy (jeśli są skupione, to będą bardziej wydajne). Jak pokazuje przykład, nie musi on przechowywać wszystkich rzeczywistych elementów (lista elementów '2 ** 30' nie może być przechowywana w pamięci RAM). Oczywiście, jeśli wiesz, ile masz przedmiotów, możesz użyć tego numeru zamiast dużej wartości losowej. – Bakuriu

+1

@ Rus925 Zaktualizowałem moją odpowiedź.AFAIK, z moich testów 95% pustych blist bierze * mniej * pamięci niż 'dict' i jest również szybszy do wyszukiwania. – Bakuriu

1

list:
1. append i pop z końca listy są szybkie
2. insert i pop od początku listy są powolne (tam jest ciężka praca gówno za te 2 funkcje)
3. lepiej jest użyć collection.degue dla 2. przypadku.

Słowniki:
4. Operacje dostępu są szybsze w porównaniu do list



zapętlenie przez słowniki i list:

  1. Słowniki używają iteritems() sposób odzyskać w tym samym czasie klucz i odpowiadająca mu wartość.
  2. Listy używają enumerate() w tym samym celu.

    Uwagi:
  3. Jeśli pytanie jest tylko o pętli prędkości, nie ma namacalna różnica między iteritems() i wyliczyć()
  4. iteritems() jest removed w Pythonie 3.x.
  5. Metoda zip() jest ciężkim procesem, którego należy unikać.
1

Myślę, że nie ma ogólnej odpowiedzi na to pytanie. Zależy to od podziału na liczby całkowite, dostępną pamięć i wymagania dotyczące wydajności. Reguły są następujące:

  • wyszukiwanie listy jest szybsze, ponieważ nie musisz obliczać wartości mieszania klucza.
  • dict może być bardziej zwarta wtedy największą wartość klucza jest duża
  • jeśli największy klucz jest bardzo duża (około 2^30) będzie tracić pamięć i system zacznie swapping co znacznie degraduje występy

Oto, co może być zasada:

  • jeśli istnieją „kilka” pustych wartości, jeśli wiesz, największy klucz będzie „racjonalnie” niski (w stosunku do pamięci, zgodzić się wydać na to) => użyj listy
  • jeśli następujące wymaganie nie jest weryfikowana i nie masz silną wymóg wydajność => użyj dict
  • jeśli żadna z 2 poprzednich założeniach są prawdziwe będzie trzeba wypróbować niektóre funkcje hash optymalizacje - i szczegółowo go poniżej

Teoria dyktowania jest tablicą, dla której indeks jest wynikiem funkcji hash zastosowanej do klucza. Algorytm Pythona jest poprawnie zoptymalizowany, ale jest to podejście ogólne. Jeśli wiesz, że masz specjalne partycje, możesz spróbować znaleźć hasz specjalnie przystosowany do swojego numeru porządkowego. Można znaleźć wskazówki, które można znaleźć w artykule w Wikipedii pod numerem Hash functions lub w starej dobrej bibliotece standardowej C hash

Powiązane problemy