MicroPython บนไมโครคอนโทรลเลอร์

MicroPython ได้รับการออกแบบให้สามารถทำงานบนไมโครคอนโทรลเลอร์ได้ อุปกรณ์เหล่านี้มีข้อจำกัดด้านฮาร์ดแวร์ที่อาจไม่คุ้นเคยสำหรับโปรแกรมเมอร์ที่คุ้นเคยกับคอมพิวเตอร์ทั่วไปมากกว่า โดยเฉพาะอย่างยิ่งปริมาณ RAM และพื้นที่จัดเก็บข้อมูลแบบ "ดิสก์" ที่ไม่ระเหย (หน่วยความจำแฟลช) มีจำกัด บทช่วยสอนนี้นำเสนอวิธีใช้ประโยชน์สูงสุดจากทรัพยากรที่มีจำกัด เนื่องจาก MicroPython ทำงานบนคอนโทรลเลอร์ที่ใช้สถาปัตยกรรมหลากหลาย วิธีการที่นำเสนอจึงเป็นแบบทั่วไป ในบางกรณีอาจจำเป็นต้องขอข้อมูลโดยละเอียดจากเอกสารเฉพาะแพลตฟอร์ม

หน่วยความจำแฟลช

บน OpenMV Cams วิธีง่ายๆ ในการแก้ปัญหาความจุที่จำกัดคือการติดตั้งการ์ด micro SD ในบางกรณีวิธีนี้อาจไม่สะดวก ไม่ว่าจะเป็นเพราะอุปกรณ์ไม่มีช่องเสียบการ์ด SD หรือเนื่องจากข้อจำกัดด้านต้นทุนหรือการใช้พลังงาน ดังนั้นจึงต้องใช้แฟลชบนชิปแทน เฟิร์มแวร์รวมถึงระบบย่อย MicroPython จะถูกจัดเก็บไว้ในแฟลชออนบอร์ด ความจุที่เหลือพร้อมสำหรับการใช้งาน ด้วยเหตุผลที่เกี่ยวข้องกับสถาปัตยกรรมทางกายภาพของหน่วยความจำแฟลช ความจุบางส่วนอาจไม่สามารถเข้าถึงได้ในรูปแบบไฟล์ซิสเต็ม ในกรณีเช่นนี้ พื้นที่นี้อาจถูกใช้โดยการรวมโมดูลผู้ใช้เข้าในการสร้างเฟิร์มแวร์ซึ่งจะถูกแฟลชไปยังอุปกรณ์

มีสองวิธีในการทำเช่นนี้ ได้แก่ frozen modules และ frozen bytecode วิธี frozen modules จะจัดเก็บซอร์สโค้ด Python ไว้พร้อมกับเฟิร์มแวร์ ส่วน frozen bytecode ใช้ cross compiler เพื่อแปลงซอร์สโค้ดเป็น bytecode ซึ่งจะถูกจัดเก็บไว้พร้อมกับเฟิร์มแวร์ ในกรณีใดก็ตาม โมดูลสามารถเข้าถึงได้ด้วยคำสั่ง import:

import mymodule

ขั้นตอนการสร้าง frozen modules และ bytecode ขึ้นอยู่กับแพลตฟอร์ม คำแนะนำในการสร้างเฟิร์มแวร์สามารถพบได้ในไฟล์ README ในส่วนที่เกี่ยวข้องของซอร์สทรี

โดยทั่วไปขั้นตอนมีดังนี้:

  • โคลน MicroPython repository

  • รับ toolchain (เฉพาะแพลตฟอร์ม) เพื่อสร้างเฟิร์มแวร์

  • สร้าง cross compiler

  • วางโมดูลที่ต้องการ freeze ไว้ในไดเรกทอรีที่กำหนด (ขึ้นอยู่กับว่าโมดูลจะถูก freeze เป็นซอร์สหรือเป็น bytecode)

  • สร้างเฟิร์มแวร์ อาจต้องใช้คำสั่งเฉพาะเพื่อสร้าง frozen code ทั้งสองประเภท โปรดดูเอกสารของแพลตฟอร์ม

  • แฟลชเฟิร์มแวร์ไปยังอุปกรณ์

RAM

เมื่อลด RAM usage มีสองระยะที่ต้องพิจารณา ได้แก่ การคอมไพล์และการประมวลผล นอกจากการใช้หน่วยความจำแล้ว ยังมีปัญหาที่เรียกว่า heap fragmentation โดยทั่วไปควรลดการสร้างและทำลาย object ซ้ำๆ เหตุผลนี้ครอบคลุมในส่วนที่พูดถึง heap

ระยะการคอมไพล์

เมื่อนำเข้าโมดูล MicroPython จะคอมไพล์โค้ดเป็น bytecode ซึ่งจะถูกประมวลผลโดย MicroPython virtual machine (VM) bytecode จะถูกจัดเก็บใน RAM ตัว compiler เองต้องการ RAM แต่ RAM นี้จะพร้อมใช้งานเมื่อการคอมไพล์เสร็จสิ้น

หากมีการนำเข้าโมดูลจำนวนหนึ่งแล้ว อาจเกิดสถานการณ์ที่มี RAM ไม่เพียงพอสำหรับการรัน compiler ในกรณีนี้คำสั่ง import จะสร้าง memory exception

หากโมดูลสร้าง global object ในขณะนำเข้า โมดูลจะใช้ RAM ในเวลาที่นำเข้า ซึ่งจะไม่พร้อมใช้งานสำหรับ compiler ในการนำเข้าครั้งต่อๆ ไป โดยทั่วไปควรหลีกเลี่ยงโค้ดที่ทำงานเมื่อนำเข้า วิธีที่ดีกว่าคือมีโค้ดเริ่มต้นระบบที่แอปพลิเคชันเรียกใช้หลังจากนำเข้าโมดูลทั้งหมดแล้ว วิธีนี้จะเพิ่ม RAM ที่พร้อมใช้งานสำหรับ compiler ให้มากที่สุด

หาก RAM ยังไม่เพียงพอสำหรับการคอมไพล์โมดูลทั้งหมด วิธีแก้ปัญหาหนึ่งคือ precompile โมดูล MicroPython มี cross compiler ที่สามารถคอมไพล์โมดูล Python เป็น bytecode (ดู README ในไดเรกทอรี mpy-cross) ไฟล์ bytecode ที่ได้จะมีนามสกุล .mpy สามารถคัดลอกไปยังไฟล์ซิสเต็มและนำเข้าได้ตามปกติ หรืออาจใช้งานโมดูลบางส่วนหรือทั้งหมดเป็น frozen bytecode ซึ่งบนแพลตฟอร์มส่วนใหญ่จะประหยัด RAM มากกว่า เนื่องจาก bytecode จะรันโดยตรงจากแฟลชแทนที่จะถูกจัดเก็บใน RAM

ระยะการประมวลผล

มีเทคนิคการเขียนโค้ดหลายอย่างเพื่อลด RAM usage

ค่าคงที่

MicroPython มีคีย์เวิร์ด const ซึ่งสามารถใช้ดังนี้:

from micropython import const
ROWS = const(33)
_COLS = const(0x10)
a = ROWS
b = _COLS

ในทั้งสองกรณีที่กำหนดค่าคงที่ให้กับตัวแปร compiler จะหลีกเลี่ยงการเขียนโค้ดสำหรับการค้นหาชื่อค่าคงที่โดยแทนที่ด้วยค่าตัวอักษรโดยตรง วิธีนี้ประหยัด bytecode และ RAM อย่างไรก็ตาม ค่า ROWS จะใช้พื้นที่อย่างน้อยสอง machine word หนึ่งสำหรับ key และหนึ่งสำหรับ value ใน globals dictionary การมีอยู่ใน dictionary เป็นสิ่งจำเป็นเพราะโมดูลอื่นอาจ import หรือใช้งานได้ สามารถประหยัด RAM นี้ได้โดยการเพิ่มขีดล่างนำหน้าชื่อดังเช่น _COLS สัญลักษณ์นี้ไม่สามารถมองเห็นได้จากภายนอกโมดูล จึงไม่ใช้ RAM

อาร์กิวเมนต์ของ const() อาจเป็นอะไรก็ได้ที่ในเวลาคอมไพล์ประเมินเป็นค่าคงที่ เช่น 0x100, 1 << 8 หรือ (True, "string", b"bytes") (ดูส่วนด้านล่างสำหรับรายละเอียด) ยังสามารถรวมสัญลักษณ์ const อื่นๆ ที่กำหนดไว้แล้วได้ด้วย เช่น 1 << BIT

โครงสร้างข้อมูลคงที่

เมื่อมีข้อมูลคงที่จำนวนมากและแพลตฟอร์มรองรับการประมวลผลจาก Flash สามารถประหยัด RAM ได้ดังนี้ ข้อมูลควรอยู่ในโมดูล Python และ freeze เป็น bytecode ข้อมูลต้องถูกกำหนดเป็น object bytes compiler 'รู้' ว่า object bytes ไม่สามารถเปลี่ยนแปลงได้และรับรองว่า object จะคงอยู่ในหน่วยความจำแฟลชแทนที่จะถูกคัดลอกไปยัง RAM โมดูล struct ช่วยในการแปลงระหว่างประเภท bytes และประเภทบิลท์อินอื่นๆ ของ Python

เมื่อพิจารณาผลกระทบของ frozen bytecode โปรดทราบว่าใน Python สตริง, floats, bytes, integers, complex numbers และ tuples ไม่สามารถเปลี่ยนแปลงได้ ดังนั้นสิ่งเหล่านี้จะถูก freeze เข้าไปในแฟลช (สำหรับ tuples เฉพาะเมื่อ elements ทั้งหมดไม่สามารถเปลี่ยนแปลงได้) ดังนั้นในบรรทัด

mystring = "The quick brown fox"

สตริงจริง "The quick brown fox" จะอาศัยอยู่ในแฟลช ในขณะ runtime การอ้างอิงถึงสตริงจะถูกกำหนดให้กับ ตัวแปร mystring การอ้างอิงนั้นใช้พื้นที่เพียง machine word เดียว ในหลักการ สามารถใช้จำนวนเต็มขนาดใหญ่เพื่อจัดเก็บข้อมูลคงที่ได้:

bar = 0xDEADBEEF0000DEADBEEF

เช่นเดียวกับตัวอย่างสตริง ในขณะ runtime การอ้างอิงถึงจำนวนเต็มขนาดใหญ่ตามอำเภอใจจะถูกกำหนดให้กับตัวแปร bar การอ้างอิงนั้นใช้พื้นที่เพียง machine word เดียว

Tuples ของ object คงที่นั้นคงที่ในตัวเอง tuple คงที่เหล่านั้นได้รับการปรับแต่งโดย compiler เพื่อไม่ต้องสร้างใหม่ในขณะ runtime ทุกครั้งที่ใช้ ตัวอย่างเช่น:

foo = (1, 2, 3, 4, 5, 6, 100000, ("string", b"bytes", False, True))

tuple ทั้งหมดนี้จะมีอยู่เป็น object เดียว (อาจอยู่ในแฟลชหากโค้ดถูก freeze) และถูกอ้างอิงทุกครั้งที่ต้องการ

การสร้าง object ที่ไม่จำเป็น

มีหลายสถานการณ์ที่ object อาจถูกสร้างและทำลายโดยไม่ตั้งใจ วิธีนี้อาจลดความสามารถในการใช้ RAM ผ่านการ fragmentation ส่วนต่อไปนี้จะพูดถึงตัวอย่างของสิ่งนี้

การต่อสตริง

พิจารณา code fragment ต่อไปนี้ที่มีจุดประสงค์เพื่อสร้างสตริงคงที่:

var = "foo" + "bar"
var1 = "foo" "bar"
var2 = """\
foo\
bar"""

แต่ละอย่างให้ผลลัพธ์เหมือนกัน อย่างไรก็ตามอันแรกสร้าง string object สองชิ้นที่ไม่จำเป็นในขณะ runtime และจัดสรร RAM มากขึ้นสำหรับการต่อก่อนที่จะสร้างชิ้นที่สาม ส่วนอื่นๆ ทำการต่อในขณะคอมไพล์ซึ่งมีประสิทธิภาพมากกว่า ลด fragmentation

เมื่อต้องสร้างสตริงแบบไดนามิกก่อนที่จะส่งไปยัง stream เช่นไฟล์ การทำทีละส่วนจะประหยัด RAM แทนที่จะสร้าง string object ขนาดใหญ่ ให้สร้าง substring และส่งไปยัง stream ก่อนที่จะจัดการกับส่วนต่อไป

วิธีที่ดีที่สุดในการสร้างสตริงแบบไดนามิกคือการใช้เมธอด format() ของสตริง:

var = "Temperature {:5.2f} Pressure {:06d}\n".format(temp, press)

บัฟเฟอร์

เมื่อเข้าถึงอุปกรณ์เช่น instances ของ UART, I2C และ SPI interfaces การใช้บัฟเฟอร์ที่จัดสรรไว้ล่วงหน้าจะหลีกเลี่ยงการสร้าง object ที่ไม่จำเป็น พิจารณา loop สองอันนี้:

while True:
    var = spi.read(100)
    # process data

buf = bytearray(100)
while True:
    spi.readinto(buf)
    # process data in buf

อันแรกสร้างบัฟเฟอร์ในแต่ละรอบในขณะที่อันที่สองนำบัฟเฟอร์ที่จัดสรรไว้ล่วงหน้ากลับมาใช้ใหม่ วิธีนี้ทั้งเร็วกว่าและมีประสิทธิภาพมากกว่าในแง่ของ memory fragmentation

Bytes มีขนาดเล็กกว่า ints

บนแพลตฟอร์มส่วนใหญ่ integer ใช้พื้นที่สี่ไบต์ พิจารณาสามการเรียกใช้ฟังก์ชัน foo():

def foo(bar):
    for x in bar:
        print(x)
foo([1, 2, 0xff])
foo((1, 2, 0xff))
foo(b'\1\2\xff')

ในการเรียกครั้งแรก list ของ integers จะถูกสร้างใน RAM ทุกครั้งที่โค้ดถูกรัน การเรียกครั้งที่สองสร้าง tuple object คงที่ (a tuple ที่มีเฉพาะ object คงที่) ในระยะการคอมไพล์ ดังนั้นจึงสร้างเพียงครั้งเดียวและมีประสิทธิภาพมากกว่า list การเรียกครั้งที่สามสร้าง bytes object อย่างมีประสิทธิภาพโดยใช้ RAM น้อยที่สุด หากโมดูลถูก freeze เป็น bytecode ทั้ง tuple และ bytes object จะอยู่ในแฟลช

สตริงกับ Bytes

Python3 ได้นำการรองรับ Unicode มาใช้ ซึ่งทำให้เกิดความแตกต่างระหว่างสตริงและอาร์เรย์ของ bytes MicroPython รับรองว่า Unicode strings ไม่ใช้พื้นที่เพิ่มเติมตราบใดที่อักขระทั้งหมดในสตริงเป็น ASCII (เช่น มีค่า < 128) หากต้องการค่าในช่วง 8 บิตเต็ม สามารถใช้ object bytes และ bytearray เพื่อรับรองว่าไม่ต้องการพื้นที่เพิ่มเติม โปรดทราบว่าเมธอดสตริงส่วนใหญ่ (เช่น str.strip()) ใช้ได้กับ instances bytes ด้วย ดังนั้นกระบวนการกำจัด Unicode อาจไม่เจ็บปวด

s = 'the quick brown fox'   # A string instance
b = b'the quick brown fox'  # A bytes instance

เมื่อจำเป็นต้องแปลงระหว่างสตริงและ bytes สามารถใช้เมธอด str.encode() และ bytes.decode() ได้ โปรดทราบว่าทั้งสตริงและ bytes ไม่สามารถเปลี่ยนแปลงได้ การดำเนินการใดๆ ที่รับ object ดังกล่าวเป็นอินพุตและสร้างอีก object หนึ่งจะต้องมีการจัดสรร RAM อย่างน้อยหนึ่งครั้งเพื่อสร้างผลลัพธ์ ในบรรทัดที่สองด้านล่าง bytes object ใหม่จะถูกจัดสรร สิ่งนี้จะเกิดขึ้นด้วยหาก foo เป็นสตริง

foo = b'   empty whitespace'
foo = foo.lstrip()

การประมวลผล compiler ในขณะ runtime

ฟังก์ชัน Python eval และ exec เรียกใช้ compiler ในขณะ runtime ซึ่งต้องการ RAM จำนวนมาก โปรดทราบว่าไลบรารี pickle จาก micropython-lib ใช้ exec อาจมีประสิทธิภาพ RAM มากกว่าหากใช้ไลบรารี json สำหรับการ serialise object

การจัดเก็บสตริงในแฟลช

Python strings ไม่สามารถเปลี่ยนแปลงได้จึงมีศักยภาพที่จะถูกจัดเก็บในหน่วยความจำแบบอ่านอย่างเดียว compiler สามารถวางสตริงที่กำหนดในโค้ด Python ไว้ในแฟลชได้ เช่นเดียวกับ frozen modules จำเป็นต้องมีสำเนาของ source tree บน PC และ toolchain เพื่อสร้างเฟิร์มแวร์ ขั้นตอนนี้จะทำงานแม้ว่าโมดูลจะยังไม่ได้รับการดีบักอย่างสมบูรณ์ ตราบใดที่สามารถ import และรันได้

หลังจาก import โมดูลแล้ว ให้รัน:

micropython.qstr_info(1)

จากนั้นคัดลอกและวางบรรทัด Q(xxx) ทั้งหมดลงในโปรแกรมแก้ไขข้อความ ตรวจสอบและลบบรรทัดที่ไม่ถูกต้องอย่างชัดเจน เปิดไฟล์ qstrdefsport.h ซึ่งจะพบได้ใน ports/stm32 (หรือไดเรกทอรีที่เทียบเท่าสำหรับสถาปัตยกรรมที่ใช้งาน) คัดลอกและวางบรรทัดที่แก้ไขแล้วที่ท้ายไฟล์ บันทึกไฟล์ สร้างใหม่และแฟลชเฟิร์มแวร์ สามารถตรวจสอบผลลัพธ์ได้โดยการ import โมดูลและออกคำสั่งอีกครั้ง:

micropython.qstr_info(1)

บรรทัด Q(xxx) ควรหายไป

Heap

เมื่อโปรแกรมที่กำลังทำงานสร้าง object RAM ที่จำเป็นจะถูกจัดสรรจาก pool ขนาดคงที่ที่เรียกว่า heap เมื่อ object ออกนอก scope (กล่าวคือไม่สามารถเข้าถึงได้โดยโค้ด) object ที่ซ้ำซ้อนนั้นเรียกว่า "garbage" กระบวนการที่เรียกว่า "garbage collection" (GC) จะคืนหน่วยความจำนั้น ส่งคืนไปยัง free heap กระบวนการนี้ทำงานโดยอัตโนมัติ อย่างไรก็ตามสามารถเรียกใช้โดยตรงได้โดยออกคำสั่ง gc.collect()

การพูดคุยในเรื่องนี้ค่อนข้างซับซ้อน สำหรับ 'quick fix' ให้ออกคำสั่งต่อไปนี้เป็นระยะ:

gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())

สำหรับข้อมูลเพิ่มเติม ดูด้านล่างและเอกสารสำหรับโมดูลบิลท์อิน gc

สำหรับรายละเอียดจากมุมมองภายในของ MicroPython/นักพัฒนา โปรดดูเพิ่มเติมที่ การจัดการหน่วยความจำ

Fragmentation

สมมติว่าโปรแกรมสร้าง object foo แล้วสร้าง object bar ต่อมา foo ออกนอก scope แต่ bar ยังคงอยู่ RAM ที่ใช้โดย foo จะถูก GC คืนมา อย่างไรก็ตาม หาก bar ถูกจัดสรรไปยังที่อยู่สูงกว่า RAM ที่คืนมาจาก foo จะมีประโยชน์เฉพาะสำหรับ object ที่ไม่ใหญ่กว่า foo ในโปรแกรมที่ซับซ้อนหรือทำงานนานๆ heap อาจเกิด fragmentation แม้จะมี RAM ที่พร้อมใช้งานจำนวนมาก แต่ไม่มีพื้นที่ต่อเนื่องเพียงพอสำหรับจัดสรร object เฉพาะ และโปรแกรมจะล้มเหลวด้วย memory error

เทคนิคที่อธิบายข้างต้นมีจุดมุ่งหมายเพื่อลดปัญหานี้ เมื่อต้องการบัฟเฟอร์ถาวรขนาดใหญ่หรือ object อื่นๆ ควรสร้างเหล่านี้ตั้งแต่ต้นในกระบวนการรันโปรแกรมก่อนที่จะเกิด fragmentation การปรับปรุงเพิ่มเติมสามารถทำได้โดยการตรวจสอบสถานะของ heap และการควบคุม GC ซึ่งจะอธิบายด้านล่าง

การรายงาน

มีฟังก์ชันไลบรารีหลายอย่างสำหรับรายงานการจัดสรรหน่วยความจำและควบคุม GC ฟังก์ชันเหล่านี้พบได้ในโมดูล gc และ micropython ตัวอย่างต่อไปนี้สามารถวางที่ REPL (Ctrl-E เพื่อเข้าสู่โหมด paste, Ctrl-D เพื่อรัน)

import gc
import micropython
gc.collect()
micropython.mem_info()
print('-----------------------------')
print('Initial free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
def func():
    a = bytearray(10000)
gc.collect()
print('Func definition: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
func()
print('Func run free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
gc.collect()
print('Garbage collect free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
print('-----------------------------')
micropython.mem_info(1)

เมธอดที่ใช้ข้างต้น:

  • gc.collect() บังคับให้ garbage collection ดูเชิงอรรถ

  • micropython.mem_info() พิมพ์สรุปการใช้งาน RAM

  • gc.mem_free() คืนขนาด free heap เป็นไบต์

  • gc.mem_alloc() คืนจำนวนไบต์ที่จัดสรรในปัจจุบัน

  • micropython.mem_info(1) พิมพ์ตารางการใช้งาน heap (มีรายละเอียดด้านล่าง)

ตัวเลขที่ได้ขึ้นอยู่กับแพลตฟอร์ม แต่จะเห็นได้ว่าการประกาศฟังก์ชันใช้ RAM เพียงเล็กน้อยในรูปของ bytecode ที่ compiler สร้าง (RAM ที่ compiler ใช้ได้ถูกคืนมาแล้ว) การรันฟังก์ชันใช้ RAM มากกว่า 10KiB แต่เมื่อกลับมา a จะกลายเป็น garbage เพราะออกนอก scope และไม่สามารถอ้างอิงได้ gc.collect() สุดท้ายจะคืนหน่วยความจำนั้น

ผลลัพธ์สุดท้ายที่ micropython.mem_info(1) สร้างจะแตกต่างกันในรายละเอียด แต่อาจตีความได้ดังนี้:

สัญลักษณ์

ความหมาย

.

บล็อกว่าง

h

head block

=

tail block

m

marked head block

T

tuple

L

list

D

dict

F

float

B

byte code

M

module

S

string หรือ bytes

A

bytearray

แต่ละตัวอักษรแทน memory block เดียว โดย block คือ 16 ไบต์ ดังนั้นแต่ละบรรทัดของ heap dump แทน 0x400 ไบต์ หรือ 1KiB ของ RAM

การควบคุม garbage collection

สามารถสั่ง GC ได้ตลอดเวลาโดยออกคำสั่ง gc.collect() เป็นประโยชน์ที่จะทำเช่นนี้เป็นระยะ ประการแรกเพื่อป้องกัน fragmentation ล่วงหน้า และประการที่สองเพื่อประสิทธิภาพ GC อาจใช้เวลาหลายมิลลิวินาที แต่จะเร็วกว่าเมื่อมีงานน้อย (ประมาณ 1ms บน OpenMV Cam) การเรียกใช้อย่างชัดเจนสามารถลดความล่าช้านั้นในขณะที่รับรองว่าจะเกิดขึ้น ณ จุดในโปรแกรมที่ยอมรับได้

Automatic GC จะเกิดขึ้นภายใต้สถานการณ์ต่อไปนี้ เมื่อการพยายามจัดสรรล้มเหลว GC จะถูกทำและพยายามจัดสรรใหม่ เฉพาะเมื่อสิ่งนี้ล้มเหลวเท่านั้นจึงจะเกิด exception ประการที่สอง automatic GC จะถูกเรียกใช้หาก RAM ที่ว่างลดลงต่ำกว่าค่าขีดแบ่ง ค่าขีดแบ่งนี้สามารถปรับได้ขณะที่การประมวลผลดำเนินไป:

gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())

วิธีนี้จะกระตุ้น GC เมื่อมากกว่า 25% ของ free heap ที่มีอยู่ในปัจจุบันถูกใช้งาน

โดยทั่วไปโมดูลควรสร้าง data object ในขณะ runtime โดยใช้ constructors หรือฟังก์ชันเริ่มต้นระบบอื่นๆ เหตุผลคือหากสิ่งนี้เกิดขึ้นในขณะเริ่มต้น compiler อาจขาด RAM เมื่อมีการนำเข้าโมดูลถัดไป หากโมดูล instantiate ข้อมูลในขณะ import gc.collect() ที่ออกหลังจาก import จะบรรเทาปัญหา

การดำเนินการสตริง

MicroPython จัดการสตริงอย่างมีประสิทธิภาพและการเข้าใจสิ่งนี้สามารถช่วยในการออกแบบแอปพลิเคชันสำหรับทำงานบนไมโครคอนโทรลเลอร์ได้ เมื่อโมดูลถูกคอมไพล์ สตริงที่ปรากฏหลายครั้งจะถูกจัดเก็บเพียงครั้งเดียว กระบวนการนี้เรียกว่า string interning ใน MicroPython สตริงที่ถูก intern เรียกว่า qstr ในโมดูลที่ import ตามปกติ instance เดียวนั้นจะอยู่ใน RAM แต่ตามที่อธิบายข้างต้น ในโมดูลที่ freeze เป็น bytecode จะอยู่ในแฟลช

การเปรียบเทียบสตริงยังทำอย่างมีประสิทธิภาพโดยใช้ hashing แทนการเปรียบเทียบทีละอักขระ ดังนั้นการลงโทษสำหรับการใช้สตริงแทน integers อาจมีน้อยทั้งในแง่ประสิทธิภาพและการใช้ RAM ซึ่งอาจเป็นเรื่องที่น่าแปลกใจสำหรับโปรแกรมเมอร์ C

บทส่งท้าย

MicroPython ส่ง คืน และ (โดยค่าเริ่มต้น) คัดลอก object โดยอ้างอิง การอ้างอิงใช้พื้นที่ machine word เดียว ดังนั้นกระบวนการเหล่านี้มีประสิทธิภาพในการใช้ RAM และความเร็ว

เมื่อต้องการตัวแปรที่มีขนาดไม่ใช่ byte หรือ machine word มีไลบรารีมาตรฐานที่ช่วยจัดเก็บสิ่งเหล่านี้อย่างมีประสิทธิภาพและในการแปลง ดูโมดูล array, struct และ uctypes

เชิงอรรถ: ค่าที่คืนจาก gc.collect()

บนแพลตฟอร์ม Unix และ Windows เมธอด gc.collect() จะคืนค่าเป็น integer ซึ่งแสดงจำนวนพื้นที่หน่วยความจำที่แตกต่างกันที่ถูกคืนในการ collection (อย่างแม่นยำกว่าคือจำนวน heads ที่ถูกเปลี่ยนเป็น frees) เพื่อเหตุผลด้านประสิทธิภาพ bare metal ports จะไม่คืนค่านี้