메모리 사용량 및 CPU 소비 측면에서 Python에서 더 효율적인 것은 무엇입니까?
배경 :
엄청난 양의 데이터를 Python에로드해야합니다. 필드 컨테이너 인 개체를 만들었습니다. 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']
추가 오버 헤드의 소량.
답변
namedtuple 사용을 고려해 보셨습니까 ? ( python 2.4 / 2.5 링크 )
튜플의 성능과 클래스의 편리함을 제공하는 구조화 된 데이터를 나타내는 새로운 표준 방식입니다.
사전과 비교할 때 유일한 단점은 (튜플과 같은) 생성 후 속성을 변경할 수있는 기능을 제공하지 않는다는 것입니다.
답변
다음은 파이썬 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
내 결론은 :
- 슬롯은 최고의 메모리 풋 프린트를 가지며 속도면에서 합리적입니다.
- dicts가 가장 빠르지 만 가장 많은 메모리를 사용합니다.
답변
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
답변
의문의 여지가 없습니다.
다른 속성이없는 데이터가 있습니다 (메서드도, 아무것도 없음). 따라서 데이터 컨테이너 (이 경우 사전)가 있습니다.
나는 일반적으로 데이터 모델링 측면에서 생각하는 것을 선호합니다 . 큰 성능 문제가 있으면 추상화에서 무언가를 포기할 수 있지만 아주 좋은 이유가 있어야합니다.
프로그래밍은 복잡성을 관리하고 올바른 추상화를 유지하는 것입니다. 것은 이러한 결과를 달성하는 가장 유용한 방법 중 하나입니다.
[정보 이유 객체가 느립니다, 당신의 측정이 정확하지라고 생각합니다.
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