it-swarm.com.de

Wörterbuch vs. Objekt - was ist effizienter und warum?

Was ist in Python in Bezug auf Speicherauslastung und CPU-Auslastung - Wörterbuch oder Objekt effizienter?

Hintergrund: Ich muss große Datenmengen in Python laden. Ich habe ein Objekt erstellt, das nur ein Feldcontainer ist. Das Erstellen von 4M-Instanzen und das Einfügen in ein Wörterbuch dauerte etwa 10 Minuten und ~ 6 GB Speicher. Nachdem das Wörterbuch fertig ist, ist der Zugriff auf das Wörterbuch ein Wimpernschlag.

Beispiel: Um die Leistung zu überprüfen, habe ich zwei einfache Programme geschrieben, die dasselbe tun - eines verwendet Objekte, ein anderes Wörterbuch:

Objekt (Ausführungszeit ~ 18sec):

class Obj(object):
  def __init__(self, i):
    self.i = i
    self.l = []
all = {}
for i in range(1000000):
  all[i] = Obj(i)

Wörterbuch (Ausführungszeit ~ 12sec):

all = {}
for i in range(1000000):
  o = {}
  o['i'] = i
  o['l'] = []
  all[i] = o

Frage: Mache ich etwas falsches oder Wörterbuch ist einfach schneller als Objekt? Wenn das Wörterbuch tatsächlich besser ist, kann jemand erklären, warum?

106
tkokoszka

Haben Sie es mit __slots__ versucht?

Aus der Dokumentation :

Instanzen von alten und neuen Klassen verfügen standardmäßig über ein Wörterbuch für die Attributspeicherung. Dies verschwendet Platz für Objekte mit sehr wenigen Instanzvariablen. Der Platzbedarf kann bei der Erstellung einer großen Anzahl von Instanzen akut werden.

Der Standardwert kann durch Definieren von __slots__ in einer Klassendefinition im neuen Stil überschrieben werden. Die __slots__-Deklaration benötigt eine Folge von Instanzvariablen und reserviert in jeder Instanz gerade genug Platz, um einen Wert für jede Variable zu speichern. Speicherplatz wird gespeichert, da __dict__ nicht für jede Instanz erstellt wird.

Spart das Zeit und Speicher?

Vergleich der drei Ansätze auf meinem Computer:

test_slots.py:

class Obj(object):
  __slots__ = ('i', 'l')
  def __init__(self, i):
    self.i = i
    self.l = []
all = {}
for i in range(1000000):
  all[i] = Obj(i)

test_obj.py:

class Obj(object):
  def __init__(self, i):
    self.i = i
    self.l = []
all = {}
for i in range(1000000):
  all[i] = Obj(i)

test_dict.py:

all = {}
for i in range(1000000):
  o = {}
  o['i'] = i
  o['l'] = []
  all[i] = o

test_namedtuple.py (in 2.6 unterstützt):

import collections

Obj = collections.namedtuple('Obj', 'i l')

all = {}
for i in range(1000000):
  all[i] = Obj(i, [])

Benchmark ausführen (mit CPython 2.5):

$ lshw | grep product | head -n 1
          product: Intel(R) Pentium(R) M processor 1.60GHz
$ python --version
Python 2.5
$ time python test_obj.py && time python test_dict.py && time python test_slots.py 

real    0m27.398s (using 'normal' object)
real    0m16.747s (using __dict__)
real    0m11.777s (using __slots__)

Verwenden von CPython 2.6.2 einschließlich des genannten Tuple-Tests:

$ python --version
Python 2.6.2
$ time python test_obj.py && time python test_dict.py && time python test_slots.py && time python test_namedtuple.py 

real    0m27.197s (using 'normal' object)
real    0m17.657s (using __dict__)
real    0m12.249s (using __slots__)
real    0m12.262s (using namedtuple)

Also ja (nicht wirklich eine Überraschung), __slots__ ist eine Leistungsoptimierung. Die Verwendung eines benannten Tupels hat eine ähnliche Leistung wie __slots__.

137
codeape

Der Attributzugriff in einem Objekt verwendet den Wörterbuchzugriff hinter den Kulissen. Mit dem Attributzugriff fügen Sie also zusätzlichen Aufwand hinzu. Außerdem erleiden Sie im Objektfall zusätzlichen Aufwand, weil z. zusätzliche Speicherzuweisungen und Code-Ausführung (z. B. der __init__-Methode).

Wenn o in Ihrem Code eine Obj-Instanz ist, entspricht o.attro.__dict__['attr'] mit einem geringen zusätzlichen Aufwand.

14
Vinay Sajip

Haben Sie überlegt, ein namedtuple zu verwenden? ( Link für Python 2.4/2.5 )

Dies ist die neue Standardmethode zur Darstellung strukturierter Daten, die Ihnen die Leistung eines Tuples und die Bequemlichkeit einer Klasse bietet.

Verglichen mit Wörterbüchern ist es nur ein Nachteil, dass Sie (wie Tupel) nicht die Möglichkeit haben, Attribute nach der Erstellung zu ändern.

8
John Fouhy
from datetime import datetime

ITER_COUNT = 1000 * 1000

def timeit(method):
    def timed(*args, **kw):
        s = datetime.now()
        result = method(*args, **kw)
        e = datetime.now()

        print method.__name__, '(%r, %r)' % (args, kw), e - s
        return result
    return timed

class Obj(object):
    def __init__(self, i):
       self.i = i
       self.l = []

class SlotObj(object):
    __slots__ = ('i', 'l')
    def __init__(self, i):
       self.i = i
       self.l = []

@timeit
def profile_dict_of_dict():
    return dict((i, {'i': i, 'l': []}) for i in xrange(ITER_COUNT))

@timeit
def profile_list_of_dict():
    return [{'i': i, 'l': []} for i in xrange(ITER_COUNT)]

@timeit
def profile_dict_of_obj():
    return dict((i, Obj(i)) for i in xrange(ITER_COUNT))

@timeit
def profile_list_of_obj():
    return [Obj(i) for i in xrange(ITER_COUNT)]

@timeit
def profile_dict_of_slotobj():
    return dict((i, SlotObj(i)) for i in xrange(ITER_COUNT))

@timeit
def profile_list_of_slotobj():
    return [SlotObj(i) for i in xrange(ITER_COUNT)]

if __== '__main__':
    profile_dict_of_dict()
    profile_list_of_dict()
    profile_dict_of_obj()
    profile_list_of_obj()
    profile_dict_of_slotobj()
    profile_list_of_slotobj()

Ergebnisse:

[email protected]:~$ python ~/Dropbox/src/StackOverflow/1336791.py 
profile_dict_of_dict ((), {}) 0:00:08.228094
profile_list_of_dict ((), {}) 0:00:06.040870
profile_dict_of_obj ((), {}) 0:00:11.481681
profile_list_of_obj ((), {}) 0:00:10.893125
profile_dict_of_slotobj ((), {}) 0:00:06.381897
profile_list_of_slotobj ((), {}) 0:00:05.860749
3
hughdbrown

Hier ist eine Kopie von @hughdbrown answer für Python 3.6.1. Ich habe die Zählung um das 5fache erhöht und etwas Code hinzugefügt, um den Speicherbedarf des Python-Prozesses am Ende jedes Laufs zu testen.

Bevor sich die Downvoter darauf einlassen, wies Be darauf hin, dass diese Methode zum Zählen der Größe von Objekten nicht genau ist.

from datetime import datetime
import os
import psutil

process = psutil.Process(os.getpid())


ITER_COUNT = 1000 * 1000 * 5

RESULT=None

def makeL(i):
    # Use this line to negate the effect of the strings on the test 
    # return "Python is smart and will only create one string with this line"

    # Use this if you want to see the difference with 5 million unique strings
    return "This is a sample string %s" % i

def timeit(method):
    def timed(*args, **kw):
        global RESULT
        s = datetime.now()
        RESULT = method(*args, **kw)
        e = datetime.now()

        sizeMb = process.memory_info().rss / 1024 / 1024
        sizeMbStr = "{0:,}".format(round(sizeMb, 2))

        print('Time Taken = %s, \t%s, \tSize = %s' % (e - s, method.__name__, sizeMbStr))

    return timed

class Obj(object):
    def __init__(self, i):
       self.i = i
       self.l = makeL(i)

class SlotObj(object):
    __slots__ = ('i', 'l')
    def __init__(self, i):
       self.i = i
       self.l = makeL(i)

from collections import namedtuple
NT = namedtuple("NT", ["i", 'l'])

@timeit
def profile_dict_of_nt():
    return [NT(i=i, l=makeL(i)) for i in range(ITER_COUNT)]

@timeit
def profile_list_of_nt():
    return dict((i, NT(i=i, l=makeL(i))) for i in range(ITER_COUNT))

@timeit
def profile_dict_of_dict():
    return dict((i, {'i': i, 'l': makeL(i)}) for i in range(ITER_COUNT))

@timeit
def profile_list_of_dict():
    return [{'i': i, 'l': makeL(i)} for i in range(ITER_COUNT)]

@timeit
def profile_dict_of_obj():
    return dict((i, Obj(i)) for i in range(ITER_COUNT))

@timeit
def profile_list_of_obj():
    return [Obj(i) for i in range(ITER_COUNT)]

@timeit
def profile_dict_of_slot():
    return dict((i, SlotObj(i)) for i in range(ITER_COUNT))

@timeit
def profile_list_of_slot():
    return [SlotObj(i) for i in range(ITER_COUNT)]

profile_dict_of_nt()
profile_list_of_nt()
profile_dict_of_dict()
profile_list_of_dict()
profile_dict_of_obj()
profile_list_of_obj()
profile_dict_of_slot()
profile_list_of_slot()

Und das sind meine Ergebnisse

Time Taken = 0:00:07.018720,    provile_dict_of_nt,     Size = 951.83
Time Taken = 0:00:07.716197,    provile_list_of_nt,     Size = 1,084.75
Time Taken = 0:00:03.237139,    profile_dict_of_dict,   Size = 1,926.29
Time Taken = 0:00:02.770469,    profile_list_of_dict,   Size = 1,778.58
Time Taken = 0:00:07.961045,    profile_dict_of_obj,    Size = 1,537.64
Time Taken = 0:00:05.899573,    profile_list_of_obj,    Size = 1,458.05
Time Taken = 0:00:06.567684,    profile_dict_of_slot,   Size = 1,035.65
Time Taken = 0:00:04.925101,    profile_list_of_slot,   Size = 887.49

Meine Schlussfolgerung lautet:

  1. Slots haben den besten Speicherbedarf und sind hinsichtlich der Geschwindigkeit angemessen.
  2. diktiere sind die schnellsten, verwenden aber den meisten Speicherplatz.
3
Jarrod Chesney

Es gibt keine Frage.
.__ Sie haben Daten ohne andere Attribute (keine Methoden, nichts). Daher haben Sie einen Datencontainer (in diesem Fall ein Wörterbuch).

Normalerweise denke ich lieber in Bezug auf Datenmodellierung . Wenn es ein großes Leistungsproblem gibt, kann ich etwas in der Abstraktion aufgeben, allerdings nur mit sehr guten Gründen.
Bei der Programmierung dreht sich alles um die Verwaltung der Komplexität, und die Aufrechterhaltung der korrekten Abstraktion ist sehr oft eine der nützlichsten Methoden, um ein solches Ergebnis zu erzielen.

Über die Gründe ist ein Objekt langsamer, ich denke, Ihre Messung ist nicht korrekt.
Sie führen zu wenig Zuweisungen in der for-Schleife aus. Daher ist die Zeit, die erforderlich ist, um ein Diktat (intrinsisches Objekt) und ein "benutzerdefiniertes" Objekt zu instanziieren, unterschiedlich. Obwohl sie aus sprachlicher Sicht gleich sind, haben sie eine ganz andere Implementierung.
.__ Danach sollte die Zuweisungszeit für beide fast gleich sein, da die Endmitglieder innerhalb eines Wörterbuchs verwaltet werden.

2
rob

Es gibt noch eine weitere Möglichkeit, den Speicherbedarf zu reduzieren, wenn die Datenstruktur keine Referenzzyklen enthalten soll.

Vergleichen wir zwei Klassen:

class DataItem:
    __slots__ = ('name', 'age', 'address')
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address

und

$ pip install recordclass

>>> from recordclass import structclass
>>> DataItem2 = structclass('DataItem', 'name age address')
>>> inst = DataItem('Mike', 10, 'Cherry Street 15')
>>> inst2 = DataItem2('Mike', 10, 'Cherry Street 15')
>>> print(inst2)
>>> print(sys.getsizeof(inst), sys.getsizeof(inst2))
DataItem(name='Mike', age=10, address='Cherry Street 15')
64 40

Dies wurde möglich, da structclass- basierte Klassen die zyklische Garbage Collection nicht unterstützen, was in solchen Fällen nicht erforderlich ist.

Es gibt auch einen Vorteil gegenüber __slots__- basierten Klassen: Sie können zusätzliche Attribute hinzufügen:

>>> DataItem3 = structclass('DataItem', 'name age address', usedict=True)
>>> inst3 = DataItem3('Mike', 10, 'Cherry Street 15')
>>> inst3.hobby = ['drawing', 'singing']
>>> print(inst3)
>>> print(sizeof(inst3), 'has dict:',  bool(inst3.__dict__))
DataItem(name='Mike', age=10, address='Cherry Street 15', **{'hobby': ['drawing', 'singing']})
48 has dict: True
0
intellimath