> For the complete documentation index, see [llms.txt](https://think-embedded.gitbook.io/micropython/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://think-embedded.gitbook.io/micropython/micropython-benchmarking.md).

# MicroPython Benchmarking

## กรณีในการทดสอบ

เพื่อสาธิตการทดสอบและเปรียบเทียบความเร็วหรือระยะเวลาในการทำงานของโค้ด จึงได้ออกแบบฟังก์ชันในภาษาไมโครไพธอนดังต่อไปนี้ และนำไปทดสอบกับบอร์ดไมโครคอนโทรลเลอร์ เช่น **STM32F4 / L4 / F7** และ **ESP32**

* `test1`: สร้างตารางหรือ **list** เพื่อเก็บตัวเลขแบบทศนิยม **N** จำนวน เช่น **N=1024** โดยใช้ **List Comprehension** และคำนวณค่าตามฟังก์ชัน `math.sin()` &#x20;
* `test2`: สร้างตารางหรือลิสต์ เพื่อเก็บตัวเลขจำนวนเต็มขนาด 32 บิต และเป็นเลขสุ่ม (**Pseudo-random integer numbers**) ทั้งหมด N จำนวน เช่น **N=1024** โดยใช้วิธี **List Comprehension** และเรียกใช้ฟังก์ชัน `urandom.getrandbits(32)` จากนั้นหาค่าต่ำสุดและสูงสุดในรายการตัวเลขนั้น และนำข้อมูลในลิสต์ มาเรียงลำดับโดยใช้คำสั่ง `sorted()`
* `test3`: สร้างตารางหรือลิสต์ เพื่อเก็บเลขสุ่มแบบ 32 บิต หรือ 4 ไบต์ แล้วนำไปใส่เพิ่มลงในโครงสร้างข้อมูลแบบ `bytearray` ซึ่งเป็นไบต์บัฟเฟอร์ และทำซ้ำทั้งหมด **N** ครั้ง **N=1024** (ขนาดทั้งหมด **4\*N** ไบต์) จากนั้นจึงนำไปคำนวณหาค่า **SHA256 Secure Hash Value** โดยใช้คำสั่ง `uhashlib.sha256()`&#x20;
* `test4`: ทำการเข้าและถอดรหัสข้อมูล (**Data Encryption / Decryption**) ตามลำดับ โดยใช้อัลกอริธึม **AES** และเลือกใช้งานในโหมด **CBC** ซึ่งจะต้องกำหนด **IV (Initial Vector)** ขนาด 128 บิต (16 ไบต์) และใช้ขนาดของคีย์ (**Secret Key**) 128 บิต หรือ 256 บิต เพื่อเข้ารหัสข้อมูลที่ได้สุ่มมา ทั้งหมด **N** ไบต์ เช่น **N=512**

การทดสอบในแต่ละกรณี ซึ่งเขียนอยู่ในรูปแบบของฟังก์ชัน มีอาร์กิวเมนต์เป็น **N** จะทำทั้งหมด 10 ครั้ง และมีการจับเวลาในการทำงานในแต่ละครั้ง แล้วบันทึกเก็บไว้ จากนั้นจึงมาคำนวณหาค่าเฉลี่ย ค่าต่ำสุด และค่าสูงสุด (**Min. / Avg. / Max. Execution Time**) นอกจากนั้น ยังมีการตรวจสอบและคำนวณปริมาณหน่วยความจำที่ใช้สำหรับฟังก์ชัน **(Memory Usage**)

**ข้อสังเกต**:  ถ้าไมโครคอนโทรลเลอร์มีวงจรที่ทำหน้าที่เป็น **True  Number Random Generator (Hardware RNG)** ไมโครไพธอนมีคำสั่ง `uos.urandom()` ให้ใช้งาน แต่ถ้าไม่มี ให้ใช้คำสั่งของโมดูลชื่อ `urandom` แต่เป็นการทำงานแบบ **Pseudo-Random (Software)**&#x20;

โค้ดไมโครไพธอนสำหรับทดสอบ มีดังนี้

```python
import sys
import gc
import math
import machine
import utime as time
import uhashlib as hashlib
import ubinascii as binascii
import urandom as random
import ucryptolib as cryptolib
import uos as os
import sys
  
if sys.platform.startswith('pyb'):
    import pyb
elif sys.platform == 'esp32':
    # possible CPU freq. for ESP32
    # 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz
    machine.freq(240 * 1000000 )

try:
    from uos import urandom
    # uos.urandom() on ESP32 uses the hardware RNG.
    rand_bytes = lambda n: urandom( n )
except ImportError:
    # the urandom() modules uses a pseudo-RNG.
    rand_bytes = lambda n: bytes([random.getrandbits(8) for i in range(n)])

info = os.uname()
sys_info = (info[0],info[3],)
print( 'Micropython-{} {}'.format(*sys_info) )
freq = machine.freq()
if type(freq) == list or type(freq) == tuple:
    freq = freq[0]
print( 'CPU freq.   [MHz]:', int( freq//1000000 ) )
gc.collect()
print( 'Free mem. [bytes]: {}\n'.format(gc.mem_free()) )

###########################################################
def test1(N=1024):
    # generate a table of N sine values (N is the table size.)
    values = [math.sin(math.pi*i/N) for i in range(N)]
    del values

###########################################################
def test2(N=1024):
    # generate a table of 32-bit random values (N is the table size.)
    values = [random.getrandbits(32) for i in range(N)]
    # sort the values in the table
    sorted_values = sorted(values)
    assert(sorted_values[0]  == min(values),'error')
    assert(sorted_values[-1] == max(values),'error')
    del sorted_values, values

###########################################################
def test3(N=1024):
    # calculate SHA256 hash value from random numbers
    buf = bytearray()
    for i in range(N): # 4*N bytes
        x = random.getrandbits(32)  # 32-bit integer
        buf += x.to_bytes(4,'litte') # 4 bytes
    sha = hashlib.sha256()
    sha.update( buf )
    #print( binascii.hexlify(sha.digest()).decode() )
    del buf

###########################################################
def test4(N=256): # N is the plaintext size (in bytes)
    MODE_ECB, MODE_CBC, MODE_CTR = 1, 2, 6
    BLOCK_SIZE = 16 # block size (bytes) for CBC mode
    
    mode = MODE_CBC  # use the Cipher Block Chaining (CBC) mode
    # either 128-bit (16 bytes) or 256-bit key (32 bytes)
    key = bytes([random.getrandbits(8) for i in range(32)])
    # 128-bit or 16-byte initial vector (IV)
    iv = rand_bytes(BLOCK_SIZE)
    
    # random plaintext N bytes long
    plaintext = rand_bytes( N )
    plaintext_size = len(plaintext)
    if plaintext_size % 16 != 0:
        # padding with zeros
        plaintext += bytes((16 - plaintext_size % 16)*[0x00])
    #print( '>',binascii.hexlify(plaintext).decode() )

    # create an AES object for encryption
    aes = cryptolib.aes(key,MODE_CBC,iv)

    # perform encryption
    encrypted = aes.encrypt(plaintext)
    #print( '>', binascii.hexlify(encrypted).decode() )
    # create an AES object for decryption
    aes = cryptolib.aes(key,MODE_CBC,iv)
    decrypted = aes.decrypt(encrypted)
    #print( '>', binascii.hexlify(decrypted).decode() )
    assert(decrypted==plaintext)

###########################################################
# list of the tests 
tests  = [test1, test2, test3, test4]
# specify the argument N for the tests
N_args = [1024,1024,1024,512] 

NUM_TEST_RUNS = 10
for test in tests:
    values = []
    mem_used_max = 0
    for i,arg in zip(range(NUM_TEST_RUNS),N_args):
        gc.collect()
        mem_free = gc.mem_free()
        if sys.platform.startswith( 'pyb' ):
            start = pyb.micros()
            test(arg)
            exec_time = pyb.elapsed_micros(start)
        elif sys.platform == 'esp32':
            start = time.ticks_us()
            test()
            finish = time.ticks_us()
            exec_time = time.ticks_diff( finish, start )
        else:
            exec_time = 0
        
        values.append( exec_time )
        mem_used = (mem_free - gc.mem_free())
        mem_used_max = max(mem_used_max, mem_used)
        
    # calculate min/avg/max value of execution times
    min_exec = min(values)/1000
    avg_exec = sum(values)/len(values)/1000
    max_exec = max(values)/1000
    values = (min_exec, avg_exec, max_exec)
    print( test.__name__ ) # show the test name
    print('  Exec. time: {:.3f}/{:.3f}/{:.3f} msec'.format(*values) )
    print('  Mem. used : {} bytes used (max.)'.format( mem_used_max) )
    del values

```

## ผลการทดสอบและเปรียบเทียบ

**1) บอร์ด ESP32 4MB Flash (no SPIRAM), 240MHz CPU clock**

ชิป [**ESP32**](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf) นั้น มีหน่วยความจำ **SRAM 520 KB** อยู่ภายใน แต่ใช้ **SPI (Serial) Flash** ภายนอก มีวงจรสำหรับช่วยคำนวณเกี่ยวกับความปลอดภัย (**Crypto Hardware Acceleration**) เช่น **AES Accelerator,** **SHA Accelerator**, **RSA Accelerator** และ **True RNG** เป็นต้น

ตัวอย่างข้อความเอาต์พุต มีดังนี้

`Micropython-esp32 v1.13 on 2020-09-02` \
`CPU freq. [MHz]: 240` \
`Free mem. [bytes]: 103504`\
\
`test1` \
&#x20; `Exec. time: 70.964/71.159/71.700 msec` \
&#x20; `Mem. used : 53392 bytes used (max.)` \
`test2` \
&#x20; `Exec. time: 43.571/45.809/47.708 msec` \
&#x20; `Mem. used : 33328 bytes used (max.)` \
`test3` \
&#x20; `Exec. time: 51.147/51.361/51.875 msec` \
&#x20; `Mem. used : 62240 bytes used (max.)` \
`test4` \
&#x20; `Exec. time: 1.798/1.912/2.190 msec` \
&#x20; `Mem. used : 1280 bytes used (max.)`

**2) บอร์ด WEACT STM32F411CEU6 Black Pill**

ตัวประมวผลทำงานที่ความเร็ว **96 MHz** สำหรับไมโครไพธอน ชิป [**STM32F411CE**](https://www.st.com/en/microcontrollers-microprocessors/stm32f411ce.html) มีหน่วยความจำ **512 KB of Flash / 128 KB of SRAM** อยู่ภายใน แต่ไม่มีวงจร **True RNG**&#x20;

`Micropython-pyboard v1.13-268-gf7aafc062-dirty on 2021-01-09` \
`CPU freq. [MHz]: 96` \
`Free mem. [bytes]: 78480`\
\
`test1` \
&#x20; `Exec. time: 18.795/32.791/37.515 msec` \
&#x20; `Mem. used : 53392 bytes used (max.)` \
`test2` \
&#x20; `Exec. time: 24.957/44.409/52.432 msec` \
&#x20; `Mem. used : 33424 bytes used (max.)` \
`test3` \
&#x20; `Exec. time: 29.516/59.391/69.363 msec` \
&#x20; `Mem. used : 61728 bytes used (max.)` \
`test4` \
&#x20; `Exec. time: 10.778/17.986/20.390 msec` \
&#x20; `Mem. used : 8320 bytes used (max.)`

**3) บอร์ด PYBV3 STM32F405RGT6**

ตัวประมวผลทำงานที่ความเร็ว **168 MHz** สำหรับไมโครไพธอน และชิป [**STM32F405RGT6**](https://www.st.com/en/microcontrollers-microprocessors/stm32f405rg.html) มีหน่วยความจำ **1 MB of Flash / 192 KB of SRAM** อยู่ภายใน และมีวงจร **True RNG**&#x20;

`Micropython-pyboard v1.13-268-gf7aafc062-dirty on 2021-01-09` \
`CPU freq. [MHz]: 168` \
`Free mem. [bytes]: 67440`\
\
`test1` \
&#x20; `Exec. time: 13.525/23.590/26.946 msec` \
&#x20; `Mem. used : 53392 bytes used (max.)` \
`test2` \
&#x20; `Exec. time: 15.694/28.543/34.498 msec` \
&#x20; `Mem. used : 32816 bytes used (max.)` \
`test3` \
&#x20; `Exec. time: 19.509/38.632/45.161 msec` \
&#x20; `Mem. used : 62272 bytes used (max.)` \
`test4` \
&#x20; `Exec. time: 2.367/3.671/4.106 msec` \
&#x20; `Mem. used : 4064 bytes used (max.)`

**4) บอร์ด NUCLEO-STM32L476RG**

ตัวประมวผลทำงานที่ความเร็ว **80 MHz** สำหรับไมโครไพธอน และชิป [**STM32L476RG**](https://www.st.com/en/microcontrollers-microprocessors/stm32l476rg.html) มีหน่วยความจำ **1 MB of Flash / 128 KB of SRAM** อยู่ภายใน และมีวงจร **True RNG**&#x20;

`Micropython-pyboard v1.13-268-gf7aafc062-dirty on 2021-01-09` \
`CPU freq. [MHz]: 80` \
`Free mem. [bytes]: 51088`\
\
`test1` \
&#x20; `Exec. time: 29.720/55.746/64.435 msec` \
&#x20; `Mem. used : 26704 bytes used (max.)` \
`test2` \
&#x20; `Exec. time: 36.769/63.037/73.883 msec` \
&#x20; `Mem. used : 33136 bytes used (max.)` \
`test3` \
&#x20; `Exec. time: 41.802/83.779/97.831 msec` \
&#x20; `Mem. used : 30880 bytes used (max.)` \
`test4` \
&#x20; `Exec. time: 4.102/6.259/6.983 msec` \
&#x20; `Mem. used : 4064 bytes used (max.)`

**5) บอร์ด NUCLEO-F767ZI**

ตัวประมวผลทำงานที่ความเร็ว **216 MHz** สำหรับไมโครไพธอน และชิป [**STM32F767ZI**](https://www.st.com/en/microcontrollers-microprocessors/stm32f767zi.html) มีหน่วยความจำ **2 MB of Flash / 512 KB of SRAM** อยู่ภายใน และมีวงจร **True RNG**&#x20;

`Micropython-pyboard v1.13-268-gf7aafc062-dirty on 2021-01-09` \
`CPU freq. [MHz]: 216` \
`Free mem. [bytes]: 265184`\
\
`test1` \
&#x20; `Exec. time: 6.738/11.747/13.423 msec` \
&#x20; `Mem. used : 53392 bytes used (max.)` \
`test2` \
&#x20; `Exec. time: 7.350/13.956/16.657 msec` \
&#x20; `Mem. used : 32944 bytes used (max.)` \
`test3` \
&#x20; `Exec. time: 9.509/18.470/21.515 msec` \
&#x20; `Mem. used : 61984 bytes used (max.)` \
`test4` \
&#x20; `Exec. time: 1.118/1.725/1.931 msec` \
&#x20; `Mem. used : 4064 bytes used (max.)`

จากผลการทดลอง เมื่อรันโค้ดทดสอบสำหรับไมโครไพธอน เราพอจะกล่าวได้ว่า

* **STM32F405RGT6** ทำงานได้เร็วกว่า **STM32F411CEU6** และ **STM32L476RG** แต่ยังช้ากว่า **STM32F767ZI**&#x20;
  * **STM32F767ZI** มีตัวประมวลผล **ARM Cortex-M7** และใช้ความถี่ได้สูงกว่า **STM32F4** ที่มีตัวประมวลผล **ARM Cortex-M4F**
* **STM32L476RG** ทำงานได้ช้ากว่า **STM32F411CEU6** และ **STM32F405RGT6** นั้นก็เพราะว่า มีการใช้ความถี่ของ **CPU** **clock** ที่ต่ำกว่า (**STM32L4** มี **ARM Cprtex-M4F Core** แต่ประหยัดพลังงานได้ดีกว่า **STM32F4** จึงไม่เน้นการทำงานที่ความถี่สูง)
* **STM32F411CE** ไม่มี **True RNG** จึงทำงานได้ช้ากว่า **STM32F4** ที่มี **True RNG** เมื่อจำเป็นต้องสร้างข้อมูลแบบสุ่มตัวเลข
* **STM32F4** และ **STM32L4** ทำงานได้เร็วกว่า **ESP32** ยกเว้นกรณีที่ใช้ **AES**&#x20;
  * อาจเป็นเพราะว่า **ESP32** ต้องดึงข้อมูลคำสั่งจาก **(external) Serial Flash** แม้ว่าจะมี **Cache** อยู่ภายใน และทำงานด้วยความถี่สูงกว่า (240 MHz)&#x20;
  * แต่ก็ทำงานได้ดีกว่า **STM32F4** ถ้าต้องใช้ **AES** เนื่องจากมี **AES Accelerator**
