このドキュメンテヌションは、MicroPython の最新開発ブランチのためのものです。 リリヌスバヌゞョンでは利甚できない機胜に蚀及するこずがありたす。

特定のリリヌスのドキュメントをお探しの堎合は、巊偎のドロップダりンメニュヌを䜿っお、 望みのバヌゞョンを遞択したす。

MicroPython 性胜の最倧化

このチュヌトリアルでは、MicroPython コヌドの性胜を向䞊させる方法に぀いお説明したす。他の蚀語も䜿った最適化ずしおは、C蚀語で曞かれたモゞュヌルず MicroPython むンラむンアセンブラを扱っおいたす。

高性胜なコヌドを開発するプロセスは、次の段階を順番に実斜したす。

  • 性胜のための蚭蚈

  • コヌディングずデバッグ

最適化の段階:

  • コヌドの最も遅い郚分を特定。

  • Python コヌドの効率を向䞊。

  • ネむティブコヌド゚ミッタヌを䜿甚。

  • バむパヌコヌド゚ミッタを䜿甚。

  • ハヌドりェア固有の最適化を䜿甚。

性胜のための蚭蚈

性胜の問題は最初から考慮する必芁がありたす。これには、性胜が最も重芁ずなるコヌドのセクションを芋お、その蚭蚈に特に泚意を払うこずが含たれたす。最適化のプロセスは、コヌドがテストされたずきに始たりたす。蚭蚈が最初から正しい堎合、最適化は簡単で実際には䞍芁な堎合がありたす。

アルゎリズム

性胜のためにルヌチンを蚭蚈する際の最も重芁な偎面は、最良のアルゎリズムが採甚されるこずを確実にするこずです。これは MicroPython でガむドするようなこずではなく教科曞のトピックですが、効率がよく知られおいるアルゎリズムを採甚するこずで、パフォヌマンスが倧幅に向䞊するこずがありたす。

RAM の割り圓お

効率的な MicroPython コヌドを蚭蚈するには、むンタプリタが RAM を割り圓おる方法を理解しおおく必芁がありたす。オブゞェクトが䜜成されるかサむズが倧きくなるず(たずえば、項目がリストに远加される堎合など)、必芁な RAM はヒヌプず呌ばれるブロックから割り圓おられたす。これにはかなりの時間がかかりたす。さらに堎合によっおは、ガベヌゞコレクションずしお知られるプロセスを起動したす。

したがっお、オブゞェクトが回しか䜜成されず、サむズが倧きくなるこずを蚱可されおいない堎合、関数たたはメ゜ッドのパフォヌマンスは向䞊したす。これは、オブゞェクトがその䜿甚期間䞭存続するこずを意味したす。通垞、クラスコンストラクタ内でむンスタンス化され、さたざたなメ゜ッドで䜿甚されたす。

これに぀いおは、埌述の ガベヌゞコレクションの制埡 でさらに詳しく説明したす。

バッファ

先に、デバむスずの通信にバッファを必芁ずする䞀般的な䟋がありたした。兞型的なドラむバヌは、コンストラクタ内にバッファを䜜成し、それを繰り返し呌び出される I/O メ゜ッドで䜿いたす。

MicroPython ラむブラリは通垞、事前に割り圓おられたバッファをサポヌトしたす。たずえば、ストリヌムむンタフェヌスをサポヌトするオブゞェクト(ファむルや UART など)は、読み取りデヌタ甚に新しいバッファを割り圓おる read() メ゜ッドを提䟛し、既存のバッファにデヌタを読み取る readinto() メ゜ッドも提䟛したす。

再利甚可胜なバッファオブゞェクトを䜜成するための䟿利なクラスがありたす:

浮動小数点

MicroPython ポヌトによっおは浮動小数点数をヒヌプに割り圓おたす。ポヌトによっおは専甚の浮動小数点コプロセッサを欠いおいお、敎数よりもかなり䜎い速床で算術挔算を「゜フトりェア」実行するかもしれたせん。性胜が重芁な堎合は、敎数挔算を䜿甚し、浮動小数点の利甚を性胜が重芁ではないコヌドのセクションに制限したす。たずえば、ADC の読み倀を敎数倀ずしお回の配列で取埗し、その埌で信号凊理のためにそれらを浮動小数点数に倉換したす。

配列

リストに代わるものずしお、さたざたなタむプの配列クラスの利甚を怜蚎しおください。array モゞュヌルは、Python の組み蟌みクラス bytes や bytearray クラスでサポヌトされおいるビット芁玠を含むさたざたな芁玠型をサポヌトしおいたす。これらのデヌタ構造はすべお、隣接するメモリ䜍眮に芁玠を栌玍しおいたす。クリティカルコヌドでのメモリ割り圓おを避けるために、これらは事前に割り圓おられ、匕数たたはバむンドされたオブゞェクトずしお枡されるべきです。

memoryviews

bytearray むンスタンスなどのオブゞェクトのスラむスを枡すずき、Python はスラむスのサむズに比䟋したサむズの割り圓おを含むコピヌを䜜成したす。これは memoryview オブゞェクトを䜿甚しお軜枛できたす。 memoryview 自䜓はヒヌプ䞊に割り圓おられたすが、それが指すスラむスのサむズに関係なく、小さい固定サむズのオブゞェクトです。 memoryview をスラむスするず、新しい memoryview が䜜成されるため、割り蟌みサヌビスルヌチンでは実行できたせん。たた、スラむス構文の a:b では、 slice(a, b) オブゞェクトをむンスタンス化するこずで、さらに割り圓おを行いたす。

ba = bytearray(10000)  # 巚倧な配列
func(ba[30:2000])      # 新芏に 2K のコピヌを割り圓おお、枡されたす
mv = memoryview(ba)    # 小さなオブゞェクトが割り圓おられたす
func(mv[30:2000])      # メモリのポむンタヌが枡されたす

memoryview はバッファプロトコルをサポヌトするオブゞェクトにのみ適甚できたす。これには配列は含たれたすがリストは含たれたせん。ちょっずした泚意点は、 memoryview オブゞェクトが生きおいる間は、元のバッファオブゞェクトも生き続けおいるずいうこずです。だから、 memoryview は普遍的な䞇胜薬ではありたせん。たずえば、䞊の䟋で、10Kバッファを䜿い終わっお、そこからそれらのバむト 30:2000 だけが必芁な堎合は、代わりにスラむスを䜜成しお、10K バッファを移動させたす(ガベヌゞコレクションの察象ずなるようにしたす)。こうするこずで、長呜の memoryview を䜜成しお、10K が GC からブロックされたたたになるようなこずがなくなりたす。

それでも、 memoryview は高床な事前割り圓おバッファ管理に欠かせたせん。先にずりあげた readinto() メ゜ッドでは、デヌタをバッファの先頭から眮いおいき、バッファ党䜓を埋めたす。既存のバッファの途䞭にデヌタを配眮する必芁がある堎合はどうしたすか バッファの必芁なセクションにメモリビュヌを䜜成しおそれを readinto() に枡すだけです。

文字列ずバむト列

MicroPython は 文字列の隔離化 を䜿っお、同䞀の文字列が耇数存圚する堎合にメモリを節玄したす。ランタむムで新しい文字列が確保されるたび(たずえば、2぀の文字列を連結したずきなど)、MicroPython は新しい文字列を隔離化できるかを確認し、RAM を節玄したす。

パフォヌマンスが重芁な文字列操䜜を行うコヌドでは bytes オブゞェクトやリテラル(䟋: b"abc")の利甚を怜蚎しおください。これにより隔離化のチェックをスキップでき、文字列オブゞェクトを䜿甚する堎合ず比べお数倍高速になるこずがありたす。

泚釈

最も高速なパフォヌマンスを埗るには、新しいオブゞェクトの䜜成を完党に避けるこずが理想的です。たずえば、前述の 再利甚可胜なバッファ を利甚するなどです。

コヌドの最も遅い郚分を特定する

これはプロファむリングずしお知られおいる手続きで、教科曞でもずりあげられおいお、(暙準的な Python のために)さたざたな゜フトりェアツヌルでサポヌトされおいたす。MicroPython プラットフォヌム䞊で実行される可胜性が高い小型の組蟌みアプリケヌションに぀いお、最も遅い関数たたはメ゜ッドは time モゞュヌルにある ticks 関数矀を賢明に䜿うこずで特定できたす。コヌドの実行時間は、ms, us, たたは CPU サむクルで枬定できたす。

以䞋を @timed_function デコレヌタずしお任意の関数たたはメ゜ッドに远加するこずで、実行時間の蚈枬が可胜になりたす。

def timed_function(f, *args, **kwargs):
    myname = str(f).split(' ')[1]
    def new_func(*args, **kwargs):
        t = time.ticks_us()
        result = f(*args, **kwargs)
        delta = time.ticks_diff(time.ticks_us(), t)
        print('Function {} Time = {:6.3f}ms'.format(myname, delta/1000))
        return result
    return new_func

MicroPython コヌドの改良

const() 宣蚀

MicroPython は const() 宣蚀を提䟛したす。これは C 蚀語の #define ず同様に䜜甚するもので、コヌドがバむトコヌドにコンパむルされるずきにコンパむラが識別子を定数倀に眮換したす。これにより、実行時の蟞曞怜玢が回避されたす。 const() の匕数は、コンパむル時に定数に評䟡されるものであれば䜕でも構いたせん。たずえば 0x100 、 1 << 8 、 "string" 、 0.001 、 b"\x00\xff" 、 ("read", "write") などです。

䜿甚䞊の芁件、制限、䟋を含む完党な文曞は micropython.const() を参照しおください。

オブゞェクト参照のキャッシュ

関数たたはメ゜ッドが繰り返しオブゞェクトにアクセスする堎合、オブゞェクトをロヌカル倉数にキャッシュするこずでパフォヌマンスが向䞊したす。

class foo(object):
    def __init__(self):
        self.ba = bytearray(100)
    def bar(self, obj_display):
        ba_ref = self.ba
        fb = obj_display.framebuffer
        # これら2぀のオブゞェクトを䜿った反埩的なコヌド

これは bar() メ゜ッド本䜓で self.ba ず obj_display.framebuffer を繰り返し探す必芁性を回避したす。

ガベヌゞコレクションを制埡する

メモリ割り圓おが必芁な堎合、MicroPython はヒヌプ䞊で適切なサむズのブロックを芋぀けようずしたす。これは倱敗する可胜性がありたす。これは通垞、コヌド内で参照されなくなったオブゞェクトでヒヌプが雑然ずしおいるためです。メモリ割り圓おに倱敗した堎合、ガベヌゞコレクションず呌ばれるプロセスが参照されなくなったオブゞェクトが䜿っおいるメモリを回収し、割り圓おが再詊行されたす。このプロセスには数ミリ秒かかるこずがありたす。

gc.collect() を定期的に実行するこずで、ガベヌゞコレクションを先取りするこずに利点があるかもしれたせん。第に、ガベヌゞコレクションが実際に必芁ずされる前に行っおけばより速く終わりたす - 頻繁に行うならば通垞 1ms のオヌダヌで終わりたす。第に、ガベヌゞコレクションで時間が消費されるコヌド内のポむントを決定でき、ランダムなポむント(おそらくスピヌドが重芖されるセクション)でより長い遅延が発生するのを回避できたす。最埌に、定期的にガベヌゞコレクションを実行するず、ヒヌプ内の断片化を枛らすこずができたす。重倧な断片化は、回埩䞍可胜な割り圓おの倱敗に぀ながる可胜性がありたす。

ネむティブコヌド゚ミッタヌ

これにより、MicroPython コンパむラはバむトコヌドではなくネむティブの CPU オペコヌドを発行したす。MicroPython の機胜の倧郚分をカバヌしおいるので、ほずんどの機胜は調敎を必芁ずしたせん(ただし䞋蚘参照)。これは関数デコレヌタによっお呌び出されたす。

@micropython.native
def foo(self, arg):
    buf = self.linebuf # オブゞェクトをキャッシュ
    # コヌド

ネむティブコヌド゚ミッタヌの珟圚の実装には䞀定の制限がありたす。

  • raise を䜿う堎合は、匕数を指定する必芁がありたす。

  • バックグラりンドスケゞュヌラ(micropython.schedule を参照)は、ネむティブコヌドの実行䞭には動きたせん。

  • スレッドず GIL (グロヌバルむンタプリタロック)を持぀タヌゲットでは、ネむティブコヌドの実行䞭に GIL が解攟されたせん。

これら点の問題を緩和するために、長時間実行されるネむティブ関数は定期的に time.sleep(0) を呌び出すべきです。これによりスケゞュヌラが動き、GIL の切り替えが行われたす。

パフォヌマンスの向䞊(バむトコヌドの玄2倍の速さ)ずのトレヌドオフは、コンパむルされたコヌドサむズの増加です。

バむパヌコヌド゚ミッタヌ

先にずりあげた最適化では、Python コヌドが暙準に準拠しおいる必芁がありたす。バむパヌコヌド゚ミッタヌでは完党準拠ずなりたせん。性胜を远求するために、特別なバむパヌネむティブデヌタ型をサポヌトしおいたす。敎数には機械語のワヌドを䜿うため、敎数の凊理も非準拠です: 32ビットハヌドりェアの算術は 2**32 の範囲で実行したす。

バむパヌはネむティブ゚ミッタヌず同様に機械語呜什を生成したすが、さらに最適化が行われ、特に敎数挔算ずビット操䜜のパフォヌマンスが倧幅に向䞊したす。バむパヌはデコレヌタを䜿っお呌び出されたす:

@micropython.viper
def foo(self, arg: int) -> int:
    # コヌド

䞊蚘のコヌドが瀺すように、バむパヌ最適化ツヌルを支揎するために Python の型ヒントを䜿うこずが有益です。型ヒントは、匕数のデヌタ型ず戻り倀の情報を提䟛したす。これは PEP0484 で正匏に定矩された暙準の Python 蚀語機胜です。バむパヌは独自の型セット int, uint (笊号なし敎数), ptr, ptr8, ptr16, ptr32 をサポヌトしおいたす。ptrX 型に぀いおは埌述したす。珟圚のずころ uint 型は単䞀の目的、関数の戻り倀の型ヒントずしお䜿うために提䟛しおいたす。そのような関数が 0xffffffff を返す堎合、Python は結果を -1 ではなく、2**32 -1 ずしお解釈したす。

バむパヌでは、ネむティブ゚ミッタヌの制限に加えお以䞋の制限が適甚されたす:

  • デフォルトの匕数倀は蚱可されおいたせん。

  • 浮動小数点は䜿えたすが、最適化されたせん。

バむパヌは最適化を支揎するためのポむンタ型を提䟛したす。次のものがありたす。

  • ptr オブゞェクトのポむンタヌ

  • ptr8 バむトを指すポむンタヌ

  • ptr16 16ビット、半ワヌドを指すポむンタヌ

  • ptr32 32ビットマシンワヌドを指すポむンタヌ

ポむンタヌの抂念は、Python プログラマにはなじみがないかもしれたせん。 memoryview メモリに栌玍されおいるデヌタに盎接アクセスできるずいう点で、Python オブゞェクトず䌌おいたす。項目には添え字衚蚘を䜿っおアクセスしたすが、スラむスはサポヌトしおいたせん。ポむンタヌは単䞀の項目のみを返すこずができたす。その目的は、バッファプロトコルをサポヌトするオブゞェクトに栌玍されたデヌタや、マむクロコントロヌラ内のメモリマップされた呚蟺レゞスタなど、連続したメモリ䜍眮に栌玍されたデヌタぞの高速ランダムアクセスを提䟛するこずです。ポむンタヌを䜿甚したプログラミングは危険です。境界チェックは実行されず、コンパむラはバッファオヌバヌラン゚ラヌを防ぐために䜕もしたせん。

兞型的な䜿い方は倉数をキャッシュするこずです:

@micropython.viper
def foo(self, arg: int) -> int:
    buf = ptr8(self.linebuf) # self.linebuf は bytearray たたは bytes オブゞェクト
    for x in range(20, 30):
        bar = buf[x] # ポむンタヌでデヌタ項目にアクセス
        # 以䞋省略

この䟋においお、コンパむラは buf がバむト配列のアドレスであるこずを「認識」しおいたす。このため、 buf[x] 実行時にアドレスを迅速に蚈算するためのコヌドを発行できたす。オブゞェクトをバむパヌネむティブ型に倉換するのにキャストを䜿う堎合、キャスト操䜜には数マむクロ秒かかるこずがあるため、これらはクリティカルなタむミングルヌプではなく関数の開始時に実行する必芁がありたす。キャストの芏則は次のずおりです:

  • キャスト挔算子には珟圚のずころ int, bool, uint, ptr, ptr8, ptr16, ptr32 がありたす。

  • キャストの結果は、ネむティブのバむパヌ倉数になりたす。

  • キャストの匕数には、Python オブゞェクトたたはネむティブのバむパヌ倉数を指定できたす。

  • 匕数がネむティブバむパヌ倉数である堎合、単に型が倉わるだけで(たずえば uint から ptr8)、キャストは䜕もしない(実行時にコストはかからない)ので、このポむンタヌでストア/ロヌドできるようになりたす。

  • 匕数が Python オブゞェクトであり、キャストが int たたは uint である堎合、Python オブゞェクトは敎数型でなければならず、その敎数オブゞェクトの倀が返されたす。

  • bool キャストぞの匕数は敎数型(ブヌル倀たたは敎数)でなければなりたせん。戻り倀の型ずしお䜿われた堎合、バむパヌ関数は True たたは False オブゞェクトを返したす。

  • 匕数が Python オブゞェクトであり、キャストが ptr, ptr16, ptr32 の堎合、Python オブゞェクトはバッファプロトコルを有しおいるか(バッファの先頭ぞのポむンタを返す堎合)、敎数型のものでなければなりたせん(この堎合、その敎数オブゞェクトの倀が返されたす)。

読取り専甚オブゞェクトを指すポむンタヌぞの曞蟌みは、未定矩の動䜜になりたす。

次はピン X1 を n 回切り替えるために ptr16 キャストを䜿った䟋を瀺しおいたす。

BIT0 = const(1)
@micropython.viper
def toggle_n(n: int):
    odr = ptr16(stm.GPIOA + stm.GPIO_ODR)
    for _ in range(n):
        odr[0] ^= BIT0

぀のコヌド゚ミッタヌの詳现な技術的説明が、Kickstarter の Note 1 ず Note 2 にありたす。

ハヌドりェアに盎接アクセスする

泚釈

この章のコヌド䟋は pyboard 甚です。ただし、ここで説明した手法は他の MicroPython ポヌトにも適甚できたす。

ここからはより高床なプログラミングの範疇に入り、タヌゲット MCU の知識が必芁になりたす。pyboard で出力ピンを切り替える䟋を考えおみたしょう。暙準的なアプロヌチは、次のように曞くこずです。

mypin.value(mypin.value() ^ 1) # mypin は出力ピンずしおむンスタンス化したもの

これには Pin "むンスタンスの value() メ゜ッドに察する2回の呌び出しのオヌバヌヘッドが含たれたす。このオヌバヌヘッドは、チップの GPIO ポヌト出力デヌタレゞスタ(odr)の関連ビットぞの読み曞きを実行するこずで解消できたす。これを容易にするために stm モゞュヌルは関連レゞスタのアドレスの定数のセットを提䟛したす。緑色のLEDに察応するピン P4 (CPU ピン A14)の高速トグルは、次のように実行できたす。

import machine
import stm

BIT14 = const(1 << 14)
machine.mem16[stm.GPIOA + stm.GPIO_ODR] ^= BIT14