2.40. Bộ nhớ và thu gom rác¶
MicroPython quản lý bộ nhớ theo cách tương tự CPython: mọi đối tượng đều sống trên heap, và một bộ thu gom rác (GC) giải phóng các đối tượng không còn được tham chiếu nữa. Trên thiết bị với vài trăm kilobyte RAM, việc chú ý đến cách heap đang được sử dụng đôi khi là cần thiết; trên Python máy tính để bàn, hiếm khi cần làm vậy.
2.40.1. Heap¶
Heap là vùng RAM -- trên hầu hết các camera OpenMV thực sự được chia thành nhiều hơn một bể bộ nhớ vật lý -- mà runtime phân phối cho các đối tượng Python. Mỗi khi một biểu thức Python tạo ra một đối tượng mới (danh sách, chuỗi, dict, tuple, bất cứ thứ gì không phải số nguyên nhỏ hoặc singleton), một khối byte được lấy ra từ heap để chứa nó. Khi GC nhận thấy rằng một đối tượng không thể tiếp cận được, nó trả khối đó về bể giải phóng mà nó đến từ đó.
Hai hàm trong module gc đáng biết:
gc.mem_free()-- số byte giải phóng xấp xỉ trên heap ngay bây giờ.gc.collect()-- chạy một chu kỳ thu gom ngay lập tức thay vì chờ runtime kích hoạt.
import gc
print("before:", gc.mem_free())
big = [0] * 10000
print("after :", gc.mem_free())
del big
gc.collect()
print("freed :", gc.mem_free())
Các số chính xác phụ thuộc vào bản dựng; hướng của sự thay đổi mới là điều quan trọng: phân bổ các đối tượng lớn làm giảm mem_free, và loại bỏ các tham chiếu cộng với gc.collect trả lại heap.
2.40.2. Phân mảnh¶
Bể giải phóng không phải là liên tục theo cách kỳ diệu. Khi các đối tượng xuất hiện và biến mất ở các thời điểm sống khác nhau, không gian trống vỡ thành các mảnh nhỏ hơn và nhỏ hơn ngay cả khi tổng kích thước của nó vẫn còn lớn. Phân bổ một đối tượng lớn hơn mảnh giải phóng đơn lẻ lớn nhất sẽ thất bại với MemoryError -- mặc dù về mặt kỹ thuật có đủ tổng RAM trống.
Cùng một tổng RAM trống có thể chứa một bộ đệm lớn (trái) hoặc từ chối (phải) tùy thuộc vào mức độ phân mảnh.¶
Phân mảnh chủ yếu là mối lo ngại với các tập lệnh chạy dài liên tục phân bổ và giải phóng các bộ đệm có kích thước khác nhau. Biện pháp giảm thiểu hiệu quả nhất là pre-allocate các bộ đệm tồn tại lâu dài gần đầu chương trình, trước khi nhiều phân bổ ngắn hạn có cơ hội phân tán chúng.
2.40.3. Pre-allocation¶
Hai mẫu giúp heap hoạt động tốt:
Phân bổ bộ đệm kích thước cố định một lần và tái sử dụng chúng, thay vì xây dựng một danh sách hoặc
bytearraymới mỗi lần lặp.Đưa hằng số và bảng tra cứu ra khỏi vòng lặp bên trong để chúng được tạo một lần.
buf = bytearray(64) # one allocation, reused below
def fill(value):
for i in range(len(buf)):
buf[i] = value
fill(0)
fill(255)
So sánh với phiên bản tạo bytearray mới bên trong vòng lặp: mỗi lần lặp tạo ra rác mà GC phải dọn dẹp sau này. Phiên bản pre-allocated không tạo ra rác nào cả.
2.40.4. Khi nào nên gọi gc.collect¶
gc.collect() thường tự động -- runtime kích hoạt nó khi các phân bổ không tìm thấy đủ bộ nhớ trống. Gọi nó theo cách thủ công hữu ích trong hai tình huống:
Ngay sau khi một lô đối tượng lớn đã nằm ngoài phạm vi, để giải phóng chúng ngay lập tức thay vì chờ lần phân bổ tiếp theo phải trả chi phí.
Ngay trước một phần cần biết lượng bộ nhớ trống tối đa, để tránh GC kích hoạt ở giữa một thao tác nhạy cảm với thời gian.
Rải gc.collect khắp nơi không làm cho chương trình nhanh hơn -- bản thân việc thu gom mất thời gian. Sử dụng nó có chủ đích, tại các điểm mà chi phí của một lần thu gom không theo lịch sẽ tệ hơn chi phí của một lần bạn chạy có chủ đích.