2.40. หน่วยความจำและการเก็บขยะ¶
MicroPython จัดการหน่วยความจำในลักษณะเดียวกับ CPython: ทุกอ็อบเจ็กต์อาศัยอยู่บน heap และ garbage collector (GC) จะปลดปล่อยอ็อบเจ็กต์ที่ไม่มีอะไรอ้างอิงอีกต่อไป บนอุปกรณ์ที่มี RAM ไม่กี่ร้อยกิโลไบต์ การใส่ใจการใช้งาน heap บางครั้งมีความจำเป็น; บน Python สำหรับเดสก์ท็อปแทบไม่จำเป็นเลย
2.40.1. Heap¶
Heap คือพื้นที่ RAM -- บน OpenMV cameras ส่วนใหญ่จริงๆ แล้วแบ่งออกระหว่างมากกว่าหนึ่ง memory pool ทางกายภาพ -- ที่ runtime ส่งมอบให้กับอ็อบเจ็กต์ Python ทุกครั้งที่นิพจน์ Python สร้างอ็อบเจ็กต์ใหม่ (list, string, dict, tuple, อะไรก็ตามที่ไม่ใช่จำนวนเต็มขนาดเล็กหรือ singleton) บล็อกของไบต์จะออกมาจาก heap เพื่อเก็บมัน เมื่อ GC สังเกตว่าอ็อบเจ็กต์ไม่สามารถเข้าถึงได้ มันจะคืนบล็อกไปยัง free pool ที่มันมาจาก
ฟังก์ชันสองตัวในโมดูล gc ที่ควรรู้จัก:
gc.mem_free()-- จำนวนไบต์ว่างโดยประมาณบน heap ในขณะนี้gc.collect()-- รันรอบการเก็บทันทีแทนที่จะรอให้ runtime ทริกเกอร์
import gc
print("before:", gc.mem_free())
big = [0] * 10000
print("after :", gc.mem_free())
del big
gc.collect()
print("freed :", gc.mem_free())
ตัวเลขที่แน่นอนขึ้นอยู่กับ build; ทิศทาง ของการเปลี่ยนแปลงคือสิ่งที่สำคัญ: การจัดสรรสิ่งใหญ่จะลด mem_free และการละทิ้งการอ้างอิงพร้อมกับ gc.collect คืน heap กลับมา
2.40.2. การแตกกระจาย¶
Free pool ไม่ได้อยู่ติดกันอย่างมหัศจรรย์ เมื่ออ็อบเจ็กต์มาและไปตามอายุการใช้งานที่แตกต่างกัน พื้นที่ว่างแตกออกเป็นชิ้นที่เล็กลงเรื่อยๆ แม้ขนาดรวมยังคงใหญ่อยู่ การจัดสรรอ็อบเจ็กต์ที่ใหญ่กว่าชิ้นว่างเดียวที่ใหญ่ที่สุดจะล้มเหลวด้วย MemoryError -- แม้ว่าจะมี RAM ว่างรวมกันเพียงพอในทางเทคนิค
RAM ว่างรวมเท่ากันสามารถรองรับบัฟเฟอร์ขนาดใหญ่ (ซ้าย) หรือปฏิเสธไม่ได้ (ขวา) ขึ้นอยู่กับว่ามันแตกกระจายมากแค่ไหน¶
การแตกกระจายส่วนใหญ่เป็นความกังวลสำหรับสคริปต์ที่ทำงานยาวนานซึ่งยังคงจัดสรรและคืนบัฟเฟอร์ขนาดต่างกัน การบรรเทาที่มีประสิทธิผลมากที่สุดเพียงอย่างเดียวคือ จัดสรรไว้ล่วงหน้า บัฟเฟอร์ที่มีอายุยาวใกล้กับจุดเริ่มต้นของโปรแกรม ก่อนที่การจัดสรรระยะสั้นจำนวนมากจะมีโอกาสกระจายพวกมัน
2.40.3. การจัดสรรล่วงหน้า¶
สองแนวทางทำให้ heap ทำงานได้ดี:
จัดสรรบัฟเฟอร์ขนาดคงที่ครั้งเดียวและนำกลับมาใช้ใหม่ แทนที่จะสร้าง list หรือ
bytearrayใหม่ต่อการวนซ้ำดึงค่าคงที่และตารางค้นหาออกจากลูปชั้นใน เพื่อให้สร้างเพียงครั้งเดียว
buf = bytearray(64) # one allocation, reused below
def fill(value):
for i in range(len(buf)):
buf[i] = value
fill(0)
fill(255)
เปรียบเทียบกับเวอร์ชันที่สร้าง bytearray ใหม่ภายในลูป: การวนซ้ำแต่ละครั้งสร้างขยะที่ GC ต้องทำความสะอาดในภายหลัง เวอร์ชันที่จัดสรรล่วงหน้าไม่สร้างขยะเลย
2.40.4. เมื่อใดควรเรียก gc.collect¶
gc.collect() ปกติจะทำงานอัตโนมัติ -- runtime ทริกเกอร์เมื่อการจัดสรรหาหน่วยความจำว่างไม่พอ การเรียกด้วยมือมีประโยชน์ในสองสถานการณ์:
ทันทีหลังจากที่อ็อบเจ็กต์จำนวนมากออกนอกขอบเขต เพื่อปลดปล่อยทันทีแทนที่จะรอให้การจัดสรรครั้งถัดไปต้องรับภาระ
ก่อนส่วนที่ต้องการหน่วยความจำว่างสูงสุดที่ทราบแน่นอน เพื่อหลีกเลี่ยง GC ที่ยิงขึ้นระหว่างการดำเนินการที่ต้องการเวลาที่แน่นอน
การโรยคำสั่ง gc.collect ทุกที่ไม่ได้ทำให้โปรแกรมเร็วขึ้น -- การเก็บขยะเองต้องใช้เวลา ใช้มันอย่างจงใจ ที่จุดซึ่งต้นทุนของการเก็บที่ไม่ได้กำหนดเวลาจะแย่กว่าต้นทุนของการที่คุณรันด้วยตัวเอง