2019年7月11日 星期四

MicroPython on ESP32 學習筆記 (五) : 記憶體管理

Python 好用的原因之一是具有自動垃圾收集機制, 不再使用的物件會被內建的 gc 模組自動回收以釋放所占用的記憶體, 程式員不需要去擔心記憶體是否發生洩漏 (memory leakage). MicroPython 也移植實作了 gc 的子集合 (subset), 參考 :

https://docs.micropython.org/en/latest/library/gc.html

雖然垃圾收集機制是自動的, 程式員不需要管理記憶體, 但 gc 模組亦提供 gc.collect() 方法手動回收垃圾, 避免堆積記憶體零碎化 (fragmentation), 這可提升程式的執行效能. 堆積記憶體 (heap) 為 SRAM 中用來存放物件的記憶池.

Python 程式的執行過程是先由解譯器從 REPL 介面或 Flash 記憶體讀取原始碼, 解譯器會將其編譯成 byte code 後放在 SRAM 中, 再交由 MicroPython 虛擬機器執行, 使用 import 匯入一個模組時也是如此, 因此應避免在 import 時就執行的程式碼, 特別是全域物件 (global objects) 會佔據 SRAM 記憶體空間, 影響後面模組的載入.

但良好的程式技巧才是最重要的, 應盡量避免反覆地建立物件, 參考 :

https://docs.micropython.org/en/latest/reference/constrained.html


1. 查詢 SRAM 記憶體資訊 : 

內建的 micropython 模組包含記憶體或解譯相關的方法 :

>>> import micropython 
>>> dir(micropython)     
['__class__', '__name__', 'const', 'alloc_emergency_exception_buf', 'heap_lock', 'heap_unlock', 'kbd_intr', 'mem_info', 'opt_level', 'qstr_info', 'schedule', 'stack_use']

其中 mem_info() 可以顯示記憶體的使用資訊, 即 stack (堆疊記憶區) 與 heap (堆積記憶區) 的配置與使用情形, 未傳入參數時顯示簡要資訊, 傳入 (任何) 參數時顯示完整的記憶區配置與使用情形 :

NodeMCU-32S 開發板的記憶體資訊如下 :

>>> import micropython
>>> micropython.mem_info()         #ESP32 的記憶體資訊
stack: 736 out of 15360
GC: total: 119360, used: 6192, free: 113168
 No. of 1-blocks: 48, 2-blocks: 11, max blk sz: 264, max free sz: 7065
>>> micropython.mem_info(123)    #傳入整數
stack: 752 out of 15360
GC: total: 119360, used: 7088, free: 112272
 No. of 1-blocks: 69, 2-blocks: 23, max blk sz: 264, max free sz: 7007
GC memory layout; from 3ffc2db0:
00000: MDhhhBMMDDBMhDBBBBhDh===h===Bhh==h==============================
00400: ================================================================
00800: ================================================================
00c00: ================================================================
01000: =========================================h==Bh==BBBh==BTB=h=hBh=
01400: ==TB=h========Bh=ShShh==h=======h=====hh=h=hhh=h=SLhTT=hSTBh=Bhh
01800: hBBh=h=hBh=hBh=h=hhBh=hh=Bh=hh=h=hh=h====Bh=Bh=hhBh==....h==....
01c00: ..h==...........................................................
       (108 lines all free)
1d000: ....................................
>>> micropython.mem_info('ok')       #傳入字串
stack: 752 out of 15360
GC: total: 119360, used: 6864, free: 112496
 No. of 1-blocks: 65, 2-blocks: 21, max blk sz: 264, max free sz: 7023
GC memory layout; from 3ffc2db0:
00000: MDhhhBMMDDBMhDBBBBhDh===h===Bhh==h==============================
00400: ================================================================
00800: ================================================================
00c00: ================================================================
01000: =========================================h==Bh==BBBh==BTB=h=hBh=
01400: ==TB=h========Bh=ShShh==h=======h=====hh=h=hhh=h=SLhTT=hSTBh=Bhh
01800: hBBh=h=hBh=hBh=h=hhBh=hh=Bh=hh=h=hh=h====B........h==...........
       (109 lines all free)
1d000: ....................................

可見 NodeMCU-32S 開發板的堆積記憶區 (heap) 配置約 120KB, 堆疊記憶區 (stack) 配置約 15KB, 傳入參數時不管傳入甚麼結果都一樣, 其中 "=" 表示 free 的部分.

下面是 D1 Mini 開發板與 ESP-01 模組的記憶體資訊 :

>>> import micropython
>>> micropython.mem_info() 
stack: 2112 out of 8192
GC: total: 37952, used: 5024, free: 32928
 No. of 1-blocks: 17, 2-blocks: 7, max blk sz: 264, max free sz: 2045
>>> micropython.mem_info(1)        #傳入參數
stack: 2128 out of 8192
GC: total: 37952, used: 5936, free: 32016
 No. of 1-blocks: 31, 2-blocks: 23, max blk sz: 264, max free sz: 1989
GC memory layout; from 3ffeec00:
00000: h=hhhhBMh=DhhhDBB=BBBh===h====hh=hh==h==========================
00400: ================================================================
00800: ================================================================
00c00: ================================================================
01000: =============================================h=h=hh=BhhhBBhh=Bh=
01400: h=h=h===h=hhh=Bh=h=h=h=hBh=h=h=h=........h=....h=======h=====h=.
       (31 lines all free)
09400: ....

可見 D1 Mini 開發板與 ESP-01 模組的堆積記憶區 (heap) 配置約 38KB,  堆疊記憶區 (stack) 配置約 8KB, heap 空間只也有 ESP32 的 1/3 左右 :

每 import 一個模組, heap 記憶區的 used 部分會增加, free 部分會減少, 例如 :

MicroPython v1.11-52-g205c6d0dc on 2019-06-25; ESP32 module with ESP32
Type "help()" for more information.
>>> import micropython 
>>> micropython.mem_info() 
stack: 736 out of 15360
GC: total: 119360, used: 6192, free: 113168     
 No. of 1-blocks: 48, 2-blocks: 11, max blk sz: 264, max free sz: 7065
>>> import re 
>>> micropython.mem_info() 
stack: 736 out of 15360
GC: total: 119360, used: 6384, free: 112976 
 No. of 1-blocks: 53, 2-blocks: 14, max blk sz: 264, max free sz: 7051
>>> import network   
>>> micropython.mem_info()   
stack: 736 out of 15360
GC: total: 119360, used: 6560, free: 112800   
 No. of 1-blocks: 58, 2-blocks: 17, max blk sz: 264, max free sz: 7040
>>> import ujson 
>>> micropython.mem_info() 
stack: 736 out of 15360
GC: total: 119360, used: 6752, free: 112608 
 No. of 1-blocks: 63, 2-blocks: 20, max blk sz: 264, max free sz: 7035

micropython 模組的 const 方法可用來宣告常數以便解譯器能優化記憶體管理, 但只限於整數 (int), 不可以用浮點數或, 例如 :

>>> from micropython import const    
>>> a=const(123)   
>>> print(a)      
123
>>> PI=const(3.14159)        #const() 只能傳入整數
Traceback (most recent call last):
  File "<stdin>", line 1
SyntaxError: constant must be an integer   

參考 :

http://docs.dfrobot.com/upycraft/3.1.12%20micropython.html


2. 隨手做環保 :  

手動管理記憶體的垃圾回收要先匯入 gc 模組, 用 dir() 檢視其方法, 其中 collect() 方法之功能即為手動回收不再使用的物件, 另外 mem_free() 會傳回可用之 SRAM 記憶體, 而 mem_alloc() 則傳回已被配置使用之記憶體容量, 例如 :

>>> import gc 
>>> dir(gc) 
['__class__', '__name__', 'collect', 'disable', 'enable', 'isenabled', 'mem_alloc', 'mem_free', 'threshold']
>>> gc.mem_free()   
32416   
>>> gc.mem_alloc() 
5616
>>> gc.collect()   
>>> gc.mem_free() 
32608 
>>> gc.mem_alloc() 
5424

參考 :

沒有留言 :