Jaki jest najszybszy (do dostępu) obiekt podobny do struktury w Pythonie?

Optymalizuję Kod, przez który przebiega główne wąskie gardło i uzyskuję dostęp do bardzo dużej listy obiektów podobnych do struktur. Obecnie używam namedtuples, dla czytelności. Ale niektóre szybkie benchmarking za pomocą "timeit" pokazuje, że jest to naprawdę zły sposób, aby przejść tam, gdzie wydajność jest czynnikiem: {]}

O nazwie krotka z a, b, c:

>>> timeit("z = a.c", "from __main__ import a")
0.38655471766332994

Class using __slots__, with a, b, c:

>>> timeit("z = b.c", "from __main__ import b")
0.14527461047146062

Słownik z klawiszami a, b, c:

>>> timeit("z = c['c']", "from __main__ import c")
0.11588272541098377

Krotka z trzema wartościami, przy użyciu klucz stały:

>>> timeit("z = d[2]", "from __main__ import d")
0.11106188992948773

Lista z trzema wartościami, używając klucza stałego:

>>> timeit("z = e[2]", "from __main__ import e")
0.086038238242508669

Krotka z trzema wartościami, używając klucza lokalnego:

>>> timeit("z = d[key]", "from __main__ import d, key")
0.11187358437882722

Lista z trzema wartościami, używając klucza lokalnego:

>>> timeit("z = e[key]", "from __main__ import e, key")
0.088604143037173344
Po pierwsze, czy jest coś w tych małych testach, co czyniłoby je nieważnymi? Pobiegłem każdy kilka razy, aby upewnić się, że żadne zdarzenie systemu losowego nie wyrzuciło ich, a wyniki były prawie identyczne.

Wydaje się, że słowniki oferują najlepsza równowaga między wydajnością a czytelnością, z klasami na drugim miejscu. Jest to niefortunne, ponieważ, dla moich celów, również obiekt musi być podobny do sekwencji; stąd mój wybór nazwy.

Listy są znacznie szybsze, ale klucze stałe są nie do utrzymania; musiałbym utworzyć kilka stałych indeksów, tj. KEY_1 = 1, KEY_2 = 2, itd. co też nie jest idealne.

Utknąłem z tymi wyborami, czy jest jakaś alternatywa, którą przegapiłem?

Author: DNS, 2010-04-15

5 answers

Należy pamiętać, że namedtuples są zoptymalizowane pod kątem dostępu jako krotki. Jeśli zmienisz swój accessor na a[2] zamiast a.c, zobaczysz podobną wydajność do krotek. Powodem jest to, że accessory nazw skutecznie przekładają się na wywołania do self [idx], więc płacą zarówno indeksację , jak i cenę wyszukiwania nazwy.

Jeśli twój wzorzec użycia jest taki, że dostęp po nazwie jest powszechny, ale dostęp jako krotka nie jest, możesz napisać szybki odpowiednik namedtuple, który robi rzeczy w odwrotny sposób: odracza wyszukiwanie indeksów, aby uzyskać dostęp po nazwie. Jednak wtedy zapłacisz cenę za Wyszukiwanie indeksu. Np. szybka realizacja:

def makestruct(name, fields):
    fields = fields.split()
    import textwrap
    template = textwrap.dedent("""\
    class {name}(object):
        __slots__ = {fields!r}
        def __init__(self, {args}):
            {self_fields} = {args}
        def __getitem__(self, idx): 
            return getattr(self, fields[idx])
    """).format(
        name=name,
        fields=fields,
        args=','.join(fields), 
        self_fields=','.join('self.' + f for f in fields))
    d = {'fields': fields}
    exec template in d
    return d[name]

Ale czasy są bardzo złe, kiedy __getitem__ należy nazwać:

namedtuple.a  :  0.473686933517 
namedtuple[0] :  0.180409193039
struct.a      :  0.180846214294
struct[0]     :  1.32191514969

Ie, taka sama wydajność jak klasa __slots__ dla dostępu do atrybutów( nic dziwnego - tak to jest), ale ogromne kary ze względu na podwójne wyszukiwanie w dostępach opartych na indeksach. (Godne uwagi jest to, że __slots__ naprawdę niewiele pomaga szybkość. Oszczędza pamięć, ale czas dostępu jest mniej więcej taki sam bez nich.)

Jedną trzecią opcji byłoby powielenie danych, np. podklasa z listy i przechowuje wartości zarówno w atrybutach, jak i danych List. Jednak w rzeczywistości nie uzyskujesz wydajności równoważnej z listą. Jest duży hit prędkości po prostu w subclassed (wprowadzenie kontroli dla przeciążeń pure-python). Tak więc struct [0] nadal trwa około 0.5 s (w porównaniu z 0.18 dla listy raw) w tym przypadku, a Ty podwajasz wartość zużycie pamięci, więc może to nie być tego warte.

 44
Author: Brian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-03 07:48:19

To pytanie jest dość stare (internet-time), więc pomyślałem, że spróbuję dzisiaj powtórzyć twój test, zarówno ze zwykłym Cpythonem (2.7.6), jak i z pypy (2.2.1) i zobaczę, jak różne metody się porównują. (Dodałem również w zindeksowanym wyszukiwaniu nazwanej krotki.)

Jest to trochę mikro-benchmark, więc YMMV, ale wydaje się, że pypy przyspieszył dostęp do krotki o współczynnik 30 w porównaniu z Cpythonem(podczas gdy dostęp do słownika został przyspieszony tylko o współczynnik 3).

from collections import namedtuple

STest = namedtuple("TEST", "a b c")
a = STest(a=1,b=2,c=3)

class Test(object):
    __slots__ = ["a","b","c"]

    a=1
    b=2
    c=3

b = Test()

c = {'a':1, 'b':2, 'c':3}

d = (1,2,3)
e = [1,2,3]
f = (1,2,3)
g = [1,2,3]
key = 2

if __name__ == '__main__':
    from timeit import timeit

    print("Named tuple with a, b, c:")
    print(timeit("z = a.c", "from __main__ import a"))

    print("Named tuple, using index:")
    print(timeit("z = a[2]", "from __main__ import a"))

    print("Class using __slots__, with a, b, c:")
    print(timeit("z = b.c", "from __main__ import b"))

    print("Dictionary with keys a, b, c:")
    print(timeit("z = c['c']", "from __main__ import c"))

    print("Tuple with three values, using a constant key:")    
    print(timeit("z = d[2]", "from __main__ import d"))

    print("List with three values, using a constant key:")
    print(timeit("z = e[2]", "from __main__ import e"))

    print("Tuple with three values, using a local key:")
    print(timeit("z = d[key]", "from __main__ import d, key"))

    print("List with three values, using a local key:")
    print(timeit("z = e[key]", "from __main__ import e, key"))

Python Wyniki:

Named tuple with a, b, c:
0.124072679784
Named tuple, using index:
0.0447055962367
Class using __slots__, with a, b, c:
0.0409136944224
Dictionary with keys a, b, c:
0.0412045334915
Tuple with three values, using a constant key:
0.0449477955531
List with three values, using a constant key:
0.0331083467148
Tuple with three values, using a local key:
0.0453569025139
List with three values, using a local key:
0.033030056702

Wyniki PyPy:

Named tuple with a, b, c:
0.00444889068604
Named tuple, using index:
0.00265598297119
Class using __slots__, with a, b, c:
0.00208616256714
Dictionary with keys a, b, c:
0.013897895813
Tuple with three values, using a constant key:
0.00275301933289
List with three values, using a constant key:
0.002760887146
Tuple with three values, using a local key:
0.002769947052
List with three values, using a local key:
0.00278806686401
 40
Author: Gerrat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-29 17:21:17

Kilka punktów i pomysłów:

1) masz czas na dostęp do tego samego indeksu wiele razy z rzędu. Twój rzeczywisty program prawdopodobnie używa dostępu losowego lub liniowego, który będzie miał inne zachowanie. W szczególności będzie więcej braków pamięci podręcznej procesora. Możesz uzyskać nieco inne wyniki za pomocą rzeczywistego programu.

2) OrderedDictionary jest zapisywany jako wrapper wokół dict, ergo będzie wolniejszy niż dict. To nie jest rozwiązanie.

3) Czy próbowałeś obu w Nowym Stylu a zajęcia w starym stylu? (klasy w Nowym stylu dziedziczą z object; klasy w starym stylu nie)

4) Czy próbowałeś użyć psyco lub połykać bez ładunku?

5) czy Twoja wewnętrzna pętla modyfikuje dane, czy po prostu uzyskuje do nich dostęp? Może być możliwe przekształcenie danych w najbardziej efektywną formę przed wejściem do pętli, ale użyj najwygodniejszej formy w innym miejscu programu.

 3
Author: Daniel Stutzbach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-15 16:59:20

Skusiłbym się albo (A) wymyślić jakiś rodzaj buforowania specyficznego dla obciążenia pracą i odciążyć przechowywanie i pobieranie moich danych do procesu podobnego do memcachedb, aby poprawić skalowalność, a nie samą wydajność lub (b) przepisać jako rozszerzenie C, z natywnym przechowywaniem danych. Być może Typ słownika uporządkowanego.

Możesz zacząć od tego: http://www.xs4all.nl / ~ anthon / Python / ordereddict /

 1
Author: Warren P,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-15 15:07:03

Możesz utworzyć sekwencję klas poprzez dodanie metod __iter__ i __getitem__, aby uczynić je sekwencją podobną (indeksowalną i iteratywną.)

Czy OrderedDict praca? Dostępnych jest kilka implementacji i jest on zawarty w Python31 collections moduł.

 -1
Author: mikerobi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-15 18:17:14