IT

사전 대결-어느 것이 더 처음이라고?

lottoking 2020. 7. 19. 09:03
반응형

사전 대결-어느 것이 더 처음이라고?


메모리 사용 및 CPU 소비에서 더 많은 것이 있습니까?

배경 : 엄청난 양의 데이터를로드해야합니다. 필드 컨테이너 인 객체를 만들었습니다. 4M 인스턴스를 만들어 사전에 넣는 데 약 10 분과 ~ 6GB의 메모리가 필요했습니다. 사전이 준비되면 액세스가 눈을 깜박입니다.

예 : 성능을 확인하기 위해 동일한 두 가지 간단한 프로그램을 작성했습니다. 하나는 사전을 사용하고 다른 하나는 사용합니다.

대상 (실행 시간 ~ 18 초) :

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

사전 (실행 시간 ~ 12 초) :

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

질문 : 내가 잘못하고 있거나 사전이 객체보다 빠 사용. 어떤 사람이 왜 그 이유를 설명 할 수 있습니까?


를 많이 보며 __slots__?

로부터 문서 :

기본적으로 이전 클래스와 새 스타일 클래스의 인스턴스에는 속성 저장을 사전에 있습니다. 이 인스턴스 변수는 거의없는 객체를위한 공간을 제공합니다. 많은 수의 인스턴스를 만들 때 공간 소비가 급격하게 될 수 있습니다.

__slots__새 스타일 클래스 정의에서 정의를 사용 하여 대체 할 수 있습니다 . __slots__선언 각 변수에 대한 값을 유지하도록 할당의 인스턴스 인스턴스 변수 보유 충분한 공간의 시퀀스 다. __dict__인스턴스마다 각 공간이 생성되지 않아 공간이 절약 됩니다.

아니라 메모리 아니라 시간도 절약?

내 컴퓨터의 세 가지 접근 방식 비교 :

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 (2.6에서 지원) :

import collections

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

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

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__)

명명 된 튜플 테스트를 포함하여 CPython 2.6.2 사용 :

$ 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)

그렇습니다 (실제로 놀랍지는 언어), 사용 __slots__은 성능 최적화입니다. 명명 된 튜플을 사용하면 성능이 발생합니다 __slots__.


클래스의 속성 액세스는 장면 뒤의 사전 액세스를 사용하여 오버 속성 헤드 액세스를 사용하면 추가로 발생하는 발생합니다. 또한 발생하는 경우 추가 헤드 할당 메모리 할당 및 코드 실행 (예 : __init__메서드)으로 인해 추가 오버가 발생 합니다.

경우 코드에서 o입니다 Obj인스턴스 o.attr에 해당하는 o.__dict__['attr']추가 오버 헤드의 소량.


명명 된 튜플 사용을 고려 했습니까 ? ( python 2.4 / 2.5 링크 )

튜플의 성능과 클래스의 편의성을 제공하는 구조화 된 데이터를 다양한 새로운 표준 방법입니다.

사전과 선호 할 때 생성 유일한 단점은 튜플과 같이 후 속성을 설명 수있는 기능을 제공하지는 것입니다.


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 __name__ == '__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()

결과 :

hbrown@hbrown-lpt:~$ 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.6.1에 대한 @ hughdbrown 답변의 사본입니다. 발행을 5 배로 늘리고 각 실행이 끝날 때마다 기술 프로세스의 메모리 풋 프린트를 테스트하는 코드를 추가했습니다.

다운 보터가 수행하기 위해 수행하기, 크기를 계산하는 방법이 수행되지 않습니다.

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()

그리고 이것은 내 결과입니다

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

내 결론은 :

  1. 더 높고 속도가 합리적입니다.
  2. dicts가 가장 빠르지 만 가장 많은 메모리를 사용하십시오.

의문의 여지가 없습니다.
다른 속성이없는 데이터가 있습니다 (메서드도, 아무것도 없음). 따라서 데이터 컨테이너 (이 경우 사전)가 있습니다.

나는 일반적으로 데이터 모델링의 관점에서 생각하는 것을 선호합니다 . 큰 성능 문제가 있다면 추상화에서 무언가를 포기할 수 있지만 아주 좋은 이유가 있어야합니다.
프로그래밍은 복잡성을 관리하고 올바른 추상화를 것은 이러한 결과를 얻는 가장 유용한 방법 중 하나입니다.

[정보 이유 객체가 느립니다, 당신의 측정이 정확하지라고 생각합니다.
for 루프 내에서 너무 적은 할당을 수행하고 있으므로 dict (내재 객체)와 "사용자 정의"객체를 인스턴스화하는 데 필요한 시간이 다릅니다. 언어 관점에서 볼 때 동일하지만 구현이 상당히 다릅니다.
그 후에는 최종 구성원이 사전 내에 유지되므로 할당 시간은 둘 다 거의 동일해야합니다.


데이터 구조에 참조주기가 포함되어 있지 않은 경우 메모리 사용량을 줄이는 또 다른 방법이 있습니다.

두 클래스를 비교해 보겠습니다.

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

$ 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

그 이후로 가능해졌습니다. structclass기반 클래스는 이러한 경우 필요하지 않은 순환 가비지 수집을 지원하지 않기 .

__slots__기반 클래스에 비해 한 가지 장점 이 있습니다. 추가 속성을 추가 할 수 있습니다.

>>> 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

참고 URL : https://stackoverflow.com/questions/1336791/dictionary-vs-object-which-is-more-efficient-and-why

반응형