Python có momeoryerror là giới hạn của hệ thống RAM của bạn mà bạn đã xác định thủ công với gói resource
.
Xác định lớp của bạn với các vị trí làm cho trình thông dịch Python biết rằng các thuộc tính/thành viên trong lớp của bạn đã được sửa. Và có thể dẫn đến tiết kiệm bộ nhớ đáng kể!
Bạn có thể giảm sự sáng tạo của Dict bằng phiên dịch Python bằng cách sử dụng __slot__
. Điều này sẽ nói với thông dịch viên không tạo ra dict trong nội bộ và tái sử dụng cùng một biến.
Nếu bộ nhớ được tiêu thụ bởi các quá trình Python của bạn sẽ tiếp tục phát triển theo thời gian. Đây dường như là sự kết hợp của:
- Làm thế nào bộ phân bổ bộ nhớ C trong Python hoạt động. Đây về cơ bản là phân mảnh bộ nhớ, bởi vì việc phân bổ không thể gọi ‘miễn phí trừ khi toàn bộ khối bộ nhớ không được sử dụng. Nhưng việc sử dụng bộ nhớ thường không hoàn toàn phù hợp với các đối tượng mà bạn đang tạo và sử dụng.
- Sử dụng một số chuỗi nhỏ để so sánh dữ liệu. Một quy trình được gọi là Interning được sử dụng nội bộ nhưng tạo ra nhiều chuỗi nhỏ mang lại tải cho trình thông dịch.
Cách tốt nhất là tạo luồng công nhân hoặc nhóm chủ đề đơn để thực hiện công việc của bạn và làm mất hiệu lực công nhân/giết để giải phóng các tài nguyên được đính kèm/sử dụng trong luồng công nhân.
Mã dưới đây tạo ra nhân viên chủ đề đơn:
__slot__ = ['dna1','dna2','lock','errorResultMap']
lock = threading.Lock[]
errorResultMap = []
def process_dna_compare[dna1, dna2]:
with concurrent.futures.ThreadPoolExecutor[max_workers=1] as executor:
futures = {executor.submit[getDnaDict, lock, dna_key]: dna_key for dna_key in dna1}
'''max_workers=1 will create single threadpool'''
dna_differences_map={}
count = 0
dna_processed = False;
for future in concurrent.futures.as_completed[futures]:
result_dict = future.result[]
if result_dict :
count += 1
'''Do your processing XYZ here'''
logger.info['Total dna keys processed ' + str[count]]
def getDnaDict[lock,dna_key]:
'''process dna_key here and return item'''
try:
dataItem = item[0]
return dataItem
except:
lock.acquire[]
errorResultMap.append[{'dna_key_1': '', 'dna_key_2': dna_key_2, 'dna_key_3': dna_key_3,
'dna_key_4': 'No data for dna found'}]
lock.release[]
logger.error['Error in processing dna :'+ dna_key]
pass
if __name__ == "__main__":
dna1 = '''get data for dna1'''
dna2 = '''get data for dna2'''
process_dna_compare[dna1,dna2]
if errorResultMap != []:
''' print or write to file the errorResultMap'''
Mã dưới đây sẽ giúp bạn hiểu cách sử dụng bộ nhớ:
import objgraph
import random
import inspect
class Dna[object]:
def __init__[self]:
self.val = None
def __str__[self]:
return "dna – val: {0}".format[self.val]
def f[]:
l = []
for i in range[3]:
dna = Dna[]
#print “id of dna: {0}”.format[id[dna]]
#print “dna is: {0}”.format[dna]
l.append[dna]
return l
def main[]:
d = {}
l = f[]
d['k'] = l
print["list l has {0} objects of type Dna[]".format[len[l]]]
objgraph.show_most_common_types[]
objgraph.show_backrefs[random.choice[objgraph.by_type['Dna']],
filename="dna_refs.png"]
objgraph.show_refs[d, filename='myDna-image.png']
if __name__ == "__main__":
main[]
Đầu ra để sử dụng bộ nhớ:
list l has 3 objects of type Dna[]
function 2021
wrapper_descriptor 1072
dict 998
method_descriptor 778
builtin_function_or_method 759
tuple 667
weakref 577
getset_descriptor 396
member_descriptor 296
type 180
Đọc thêm về các khe vui
Hãy nói rằng bạn muốn lưu trữ một danh sách các số nguyên trong Python:
list_of_numbers = []
for i in range[1000000]:
list_of_numbers.append[i]
Những con số đó có thể dễ dàng phù hợp với số nguyên 64 bit, vì vậy người ta sẽ hy vọng Python sẽ lưu trữ hàng triệu số nguyên đó không quá ~ 8MB: một triệu đối tượng 8 byte.
Trên thực tế, Python sử dụng nhiều RAM hơn 35 MB để lưu trữ các số này. Tại sao? Bởi vì số nguyên python là đối tượng và các đối tượng có nhiều bộ nhớ trên đầu.
Hãy cùng xem những gì mà xảy ra dưới mui xe, và sau đó sử dụng Numpy có thể loại bỏ chi phí này.
Đo lường sử dụng bộ nhớ
Nếu chúng tôi lập hồ sơ đoạn mã ở trên, thì đây là phân bổ được sử dụng:
Như bạn có thể thấy nếu bạn di chuột hoặc nhấp vào khung hình:
- ~ 8MB được phân bổ cho danh sách.
- ~ 28MB được phân bổ cho các số nguyên.
Lưu ý bên cạnh: Bạn sẽ nhận được cùng một cách sử dụng bộ nhớ nếu bạn đã làm
list[range[1000000]]
, nhưng tôi đã cấu trúc mã theo cách này để làm cho nó rõ ràng hơn nơi mỗi phần sử dụng bộ nhớ xuất hiện. You would get the same memory usage if you didlist[range[1000000]]
, but I structured the code this way to make it clearer where each chunk of memory usage came form.
Danh sách lấy nhiều bộ nhớ đó là một danh sách Python đáng ngạc nhiên về cơ bản là một loạt các gợi ý cho các đối tượng Python tùy ý. Danh sách của chúng tôi có một triệu mục, con trỏ trên các máy 64 bit hiện đại mất 8 byte, vì vậy chúng tôi đã quay trở lại RAM 8MB.
Nhưng tại sao các số nguyên tự lấy 28MB?
Điều gì làm cho một số nguyên trong Python
Chúng ta có thể đo mức sử dụng bộ nhớ của các đối tượng Python trong byte bằng cách sử dụng
import objgraph
import random
import inspect
class Dna[object]:
def __init__[self]:
self.val = None
def __str__[self]:
return "dna – val: {0}".format[self.val]
def f[]:
l = []
for i in range[3]:
dna = Dna[]
#print “id of dna: {0}”.format[id[dna]]
#print “dna is: {0}”.format[dna]
l.append[dna]
return l
def main[]:
d = {}
l = f[]
d['k'] = l
print["list l has {0} objects of type Dna[]".format[len[l]]]
objgraph.show_most_common_types[]
objgraph.show_backrefs[random.choice[objgraph.by_type['Dna']],
filename="dna_refs.png"]
objgraph.show_refs[d, filename='myDna-image.png']
if __name__ == "__main__":
main[]
0:
>>> import sys
>>> sys.getsizeof[123]
28
Vì một số nguyên nhỏ sử dụng 28 byte, bây giờ chúng ta đã biết lý do tại sao một triệu số nguyên mất 28 MB RAM. Nhưng tại sao các số nguyên Python lại mất nhiều trí nhớ?
Mỗi đối tượng trong triển khai Python mặc định, CPython, cũng là một cấu trúc
import objgraph
import random
import inspect
class Dna[object]:
def __init__[self]:
self.val = None
def __str__[self]:
return "dna – val: {0}".format[self.val]
def f[]:
l = []
for i in range[3]:
dna = Dna[]
#print “id of dna: {0}”.format[id[dna]]
#print “dna is: {0}”.format[dna]
l.append[dna]
return l
def main[]:
d = {}
l = f[]
d['k'] = l
print["list l has {0} objects of type Dna[]".format[len[l]]]
objgraph.show_most_common_types[]
objgraph.show_backrefs[random.choice[objgraph.by_type['Dna']],
filename="dna_refs.png"]
objgraph.show_refs[d, filename='myDna-image.png']
if __name__ == "__main__":
main[]
1 C hoặc một trong các biến thể của nó. Ở đây, những gì import objgraph
import random
import inspect
class Dna[object]:
def __init__[self]:
self.val = None
def __str__[self]:
return "dna – val: {0}".format[self.val]
def f[]:
l = []
for i in range[3]:
dna = Dna[]
#print “id of dna: {0}”.format[id[dna]]
#print “dna is: {0}”.format[dna]
l.append[dna]
return l
def main[]:
d = {}
l = f[]
d['k'] = l
print["list l has {0} objects of type Dna[]".format[len[l]]]
objgraph.show_most_common_types[]
objgraph.show_backrefs[random.choice[objgraph.by_type['Dna']],
filename="dna_refs.png"]
objgraph.show_refs[d, filename='myDna-image.png']
if __name__ == "__main__":
main[]
1 trông giống như:typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
Vì vậy, bất kỳ đối tượng Python nào cũng bao gồm ở mức tối thiểu:
- Một số lượng tham chiếu, theo dõi số lượng tài liệu tham khảo có đối tượng.
- Một con trỏ đến loại, trong trường hợp của chúng tôi loại số nguyên.
- Bất cứ dữ liệu nào là cần thiết để lưu trữ đối tượng.
Trên hệ điều hành 64 bit, mặc định ngày nay, hai mục đầu tiên có nghĩa là tối thiểu 16 byte chi phí cho mỗi đối tượng.
Ngay cả khi bạn có thể tìm thấy dữ liệu của mình thành một byte, điều đó không quan trọng, bạn vẫn phải chịu đựng chi phí đó.
Chuyển sang Numpy
Để tiết kiệm cho bạn chi phí đó, các mảng numpy đang lưu trữ các số don don lưu trữ tham chiếu đến các đối tượng Python, giống như một danh sách Python bình thường. Thay vào đó, numpy mảng chỉ lưu trữ các con số.
Điều đó có nghĩa là bạn không phải trả chi phí 16+ byte đó cho mỗi số trong mảng.
Ví dụ: nếu chúng ta lập cấu hình cách sử dụng bộ nhớ cho đoạn mã này:
import numpy as np
arr = np.zeros[[1000000,], dtype=np.uint64]
for i in range[1000000]:
arr[i] = i
Chúng ta có thể thấy rằng việc sử dụng bộ nhớ để tạo mảng chỉ là 8MB, như chúng ta mong đợi, cộng với chi phí bộ nhớ của việc nhập Numpy:
Lưu ý bên lề: Đây không phải là cách bạn sẽ viết mã không thành ngữ hoặc hiệu quả, tôi chỉ cấu trúc mã theo cách này cho các mục đích giáo dục. This isn’t how you would write idiomatic or efficient NumPy code, I’m just structuring the code this way for educational purposes.
Numpy để giải cứu
Đi từ 8MB đến 35MB có lẽ là thứ bạn có thể sống cùng, nhưng đi từ 8GB đến 35GB có thể sử dụng bộ nhớ quá nhiều. Vì vậy, trong khi rất nhiều lợi ích của việc sử dụng Numpy là các cải tiến hiệu suất CPU mà bạn có thể có được cho các hoạt động số, một lý do khác mà nó rất hữu ích là chi phí bộ nhớ giảm.
Nếu bạn xử lý các danh sách lớn các số trong bộ nhớ, hãy đảm bảo rằng bạn đang sử dụng các mảng Numpy. Và nếu việc sử dụng bộ nhớ vẫn còn quá cao, bạn có thể bắt đầu xem xét các cách giảm sử dụng bộ nhớ nhiều hơn, như nén trong bộ nhớ.