Co jest bardziej wydajne w Pythonie pod względem wykorzystania pamięci i zużycia procesora - słownik czy obiekt?
Tło: muszę załadować ogromną ilość danych do Pythona. Stworzyłem obiekt, który jest po prostu kontenerem pola. Utworzenie 4M instancji i umieszczenie ich w słowniku zajęło około 10 minut i ~ 6 GB pamięci. Gdy słownik jest gotowy, dostęp do niego jest w mgnieniu oka.
Przykład: Aby sprawdzić działanie, napisałem dwa proste programy, które robią to samo - jeden wykorzystuje obiekty, drugi słownik:
Obiekt (czas wykonania ~ 18 sekund):
class Obj(object):
def __init__(self, i):
self.i = i
self.l = []
all = {}
for i in range(1000000):
all[i] = Obj(i)
Słownik (czas wykonania ~ 12 sekund):
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
Pytanie: Czy robię coś źle, czy słownik jest po prostu szybszy niż obiekt? Jeśli rzeczywiście słownik działa lepiej, czy ktoś może wyjaśnić, dlaczego?
python
performance
dictionary
object
tkokoszka
źródło
źródło
Odpowiedzi:
Czy próbowałeś użyć
__slots__
?Z dokumentacji :
Czy to oszczędza czas i pamięć?
Porównanie trzech podejść na moim komputerze:
test_slots.py:
test_obj.py:
test_dict.py:
test_namedtuple.py (obsługiwane w 2.6):
Uruchom test porównawczy (używając CPython 2.5):
Korzystanie z CPython 2.6.2, w tym nazwany test krotek:
Więc tak (nie jest to niespodzianka), używanie
__slots__
to optymalizacja wydajności. Używanie nazwanej krotki ma podobną wydajność do__slots__
.źródło
Dostęp do atrybutu w obiekcie wykorzystuje dostęp do słownika w tle - więc korzystając z dostępu do atrybutów, dodajesz dodatkowe obciążenie. Dodatkowo w przypadku obiektu ponosisz dodatkowy narzut z powodu np. Dodatkowej alokacji pamięci i wykonania kodu (np.
__init__
Metody).W twoim kodzie, jeśli
o
jestObj
instancją,o.attr
jest równoważneo.__dict__['attr']
z niewielkim dodatkowym narzutem.źródło
o.__dict__["attr"]
jest tym z dodatkowym narzutem, pobierającym dodatkowy operację kodu bajtowego; obj.attr jest szybszy. (Oczywiście dostęp do atrybutów nie będzie wolniejszy niż dostęp do subskrypcji - jest to krytyczna, mocno zoptymalizowana ścieżka kodu).Czy rozważałeś użycie namedtuple ? ( link do pythona 2.4 / 2.5 )
Jest to nowy, standardowy sposób przedstawiania danych strukturalnych, który zapewnia wydajność krotki i wygodę klasy.
Jedyną wadą w porównaniu ze słownikami jest to, że (podobnie jak krotki) nie daje możliwości zmiany atrybutów po utworzeniu.
źródło
Oto kopia odpowiedzi @hughdbrown dla Pythona 3.6.1, zwiększyłem liczbę 5x i dodałem kod, aby przetestować ślad pamięci procesu Pythona na końcu każdego uruchomienia.
Zanim to zrobią spadkobiercy, pamiętaj, że ta metoda obliczania wielkości obiektów nie jest dokładna.
A to są moje wyniki
Mój wniosek jest taki:
źródło
Wyniki:
źródło
Nie ma wątpliwości.
Masz dane bez innych atrybutów (żadnych metod, nic). Stąd masz kontener danych (w tym przypadku słownik).
Zwykle wolę myśleć w kategoriach modelowania danych . Jeśli jest jakiś poważny problem z wydajnością, mogę zrezygnować z czegoś w abstrakcji, ale tylko z bardzo dobrych powodów.
Programowanie polega na zarządzaniu złożonością, a utrzymanie prawidłowej abstrakcji jest bardzo często jednym z najbardziej użytecznych sposobów osiągnięcia takiego wyniku.
Jeśli chodzi o powody, dla których obiekt jest wolniejszy, myślę, że twój pomiar jest nieprawidłowy.
Wykonujesz zbyt mało przypisań wewnątrz pętli for, dlatego widzisz inny czas potrzebny do utworzenia instancji dyktu (obiektu wewnętrznego) i obiektu „niestandardowego”. Chociaż z perspektywy językowej są takie same, mają zgoła inną implementację.
Następnie czas przypisania powinien być prawie taki sam dla obu, ponieważ ostatecznie członkowie są utrzymywani wewnątrz słownika.
źródło
Istnieje jeszcze jeden sposób na zmniejszenie zużycia pamięci, jeśli struktura danych nie zawiera cykli referencyjnych.
Porównajmy dwie klasy:
i
Stało się to możliwe, ponieważ
structclass
klasy oparte na opcjach nie obsługują cyklicznego czyszczenia pamięci, co nie jest potrzebne w takich przypadkach.Jest też jedna zaleta w porównaniu z
__slots__
klasą podstawową: możesz dodać dodatkowe atrybuty:źródło
Oto moje testy bardzo ładnego skryptu @ Jarrod-Chesney. Dla porównania uruchomiłem go również na pythonie2 z „zakresem” zastąpionym przez „xrange”.
Z ciekawości dodałem również podobne testy z OrderedDict (ordict) dla porównania.
Python 3.6.9:
Python 2.7.15+:
Tak więc w obu głównych wersjach wnioski @ Jarrod-Chesney wciąż wyglądają dobrze.
źródło