ESP32のヒープメモリ管理 その1

概要

上記の内容を中心に、実験しながら確認してみました。

スタックとヒープ、スタティックメモリ

ESP32はArduino CoreやESP-IDFを使う場合にはFreeRTOS上で動いています。一時的なメモリはタスク作成時に割り当てられたスタックを利用します。

大きめのローカル変数を確保しようとしてエラーになるのは、スタックメモリが足りていません。一方ヒープメモリはmalloc()関数などにより確保されるメモリであり、比較的大きめのサイズで利用することが多いです。

スタティックメモリはグルーバル変数などの実行前に確保されているメモリになります。

RAMの種類

ESP32には複数種類の物理RAMが搭載されており、利用用途におうじて使い分ける必要があります。

DRAM(Data RAM)は、一番一般的なメモリでデータを保持するヒープとして使われます。IRAM(Instruction RAM)は、主にプログラムなどを保持するメモリとして使われます。汎用メモリとして使うためには32ビットアライメントでアクセスする必要があります。

そしてDRAMともIRAMとも利用可能なD/IRAMもあります。

つまり、プログラムはIRAMかD/IRAMしか利用できませんが、データは制限はありますがすべてのRAMに保存することができます。

IRAM領域にデータを保存する場合には、32ビットアライメントアクセスですので、32ビット単位で確保する必要があります。8ビットや16ビット単位でアクセスしようとすると例外が発生してしまいますので、32ビット単位で読み取って、必要ないデータをマスクするなどの処理が必要になります。

この他にSPI経由で接続されたPSRAMがあります。8MBの容量を持つ、専用RAMが販売されていますが、現在4MBまでしか利用することができません。また、接続するピンも固定されているので、基本的には自分で接続することはできないと思ったほうがいいです。

ESP32-WROVER-*などのモジュールではPSRAMが搭載されています。アクセス速度は遅いのですが、メモリをたくさん使えるようになるのでカメラなどの画像を扱う場合にはPSRAM搭載モジュールを採用している場合が多いです。

メモリ用途の種類

一般的なヒープメモリの他に、特殊用途で利用可能なメモリを確保する方法が用意されています。

DMA対応メモリは、SPIやI2Sなどにハードウエアを利用したDMA転送で使える領域のメモリです。SPI接続のPSRAMはDMA転送で利用できないので除外されます。

32ビットアライメント対応メモリは、主にIRAMのことですが通常データ領域には割り当てられないので、意図的に指定することで利用することが可能です。

外部SPIメモリはPSRAMのことです。内蔵メモリよりはアクセス速度が遅いですが、大きなメモリが必要になったときには指定することができます。

メモリ状況表示

void setup() {
  Serial.begin(115200);
  delay(100);

  Serial.printf("===============================================================\n");
  Serial.printf("Mem Test\n");
  Serial.printf("===============================================================\n");

  Serial.printf("esp_get_free_heap_size()                              : %6d\n", esp_get_free_heap_size() );
  Serial.printf("esp_get_minimum_free_heap_size()                      : %6d\n", esp_get_minimum_free_heap_size() );

  //xPortGetFreeHeapSize()(データメモリ)ヒープの空きバイト数を返すFreeRTOS関数です。これはを呼び出すのと同じheap_caps_get_free_size(MALLOC_CAP_8BIT)です。
  Serial.printf("xPortGetFreeHeapSize()                                : %6d\n", xPortGetFreeHeapSize() );

  //xPortGetMinimumEverFreeHeapSize()また、関連heap_caps_get_minimum_free_size()するものを使用して、ブート以降のヒープの「最低水準点」を追跡できます。
  Serial.printf("xPortGetMinimumEverFreeHeapSize()                     : %6d\n", xPortGetMinimumEverFreeHeapSize() );

  //heap_caps_get_free_size() さまざまなメモリ機能の現在の空きメモリを返すためにも使用できます。
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_EXEC)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_EXEC) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_32BIT)             : %6d\n", heap_caps_get_free_size(MALLOC_CAP_32BIT) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_8BIT)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_8BIT) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_DMA)               : %6d\n", heap_caps_get_free_size(MALLOC_CAP_DMA) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID2)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID2) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID3)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID3) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID3)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID4) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID4)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID5) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID5)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID6) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID6)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID7) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_PID7)              : %6d\n", heap_caps_get_free_size(MALLOC_CAP_PID3) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_SPIRAM)            : %6d\n", heap_caps_get_free_size(MALLOC_CAP_SPIRAM) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_INTERNAL)          : %6d\n", heap_caps_get_free_size(MALLOC_CAP_INTERNAL) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_DEFAULT)           : %6d\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT) );
  //Serial.printf("heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT)         : %6d\n", heap_caps_get_free_size(MALLOC_CAP_IRAM_8BIT) );
  Serial.printf("heap_caps_get_free_size(MALLOC_CAP_INVALID)           : %6d\n", heap_caps_get_free_size(MALLOC_CAP_INVALID) );

  //heap_caps_get_largest_free_block()ヒープ内の最大の空きブロックを返すために使用できます。これは、現在可能な最大の単一割り当てです。この値を追跡し、合計空きヒープと比較すると、ヒープの断片化を検出できます。
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_EXEC)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_EXEC) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_32BIT)    : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_32BIT) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_DMA)      : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_DMA) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID2)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID2) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID3)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID3) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID3)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID4) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID4)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID5) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID5)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID6) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID6)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID7) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_PID7)     : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_PID3) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM)   : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT)  : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT) );
  //Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT): %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_IRAM_8BIT) );
  Serial.printf("heap_caps_get_largest_free_block(MALLOC_CAP_INVALID)  : %6d\n", heap_caps_get_largest_free_block(MALLOC_CAP_INVALID) );

  //heap_caps_get_minimum_free_size()指定された機能を持つすべての領域の合計最小空きメモリを取得します。
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT)     : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_DMA)       : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_DMA) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID2)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID2) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID3)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID3)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID4) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID4)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID5) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID5)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID6) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID6)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID7) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_PID7)      : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_PID3) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)    : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL)  : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT)   : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT) );
  //Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_IRAM_8BIT) : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_IRAM_8BIT) );
  Serial.printf("heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID)   : %6d\n", heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID) );

  //heap_caps_get_info()multi_heap_info_t上記の関数からの情報に加えて、ヒープ固有の追加データ(割り当て数など)を含む構造体を返します。
  //Skip

  // heap_caps_print_heap_info()が返す情報の要約をstdoutに出力しheap_caps_get_info()ます。
  //Serial.printf("heap_caps_print_heap_info(MALLOC_CAP_INTERNAL) :\n");
  //heap_caps_print_heap_info(MALLOC_CAP_INTERNAL);

  // heap_caps_dump()そしてheap_caps_dump_all()意志出力は、ヒープ内の各ブロックの構造に関する情報を詳述します。これは大量の出力になる可能性があることに注意してください。
  //Serial.printf("heap_caps_dump() :\n");
  //heap_caps_dump(MALLOC_CAP_INTERNAL);
}

void loop() {
}

上記がメモリ状況表示系の関数を呼び出したスケッチ例です。一般的なESP32ボードと、PSRAMを搭載したESP32ボードで実行してみます。

===============================================================
Mem Test
===============================================================
esp_get_free_heap_size()                              :  288812
esp_get_minimum_free_heap_size()                      :  283140
xPortGetFreeHeapSize()                                :  288812
xPortGetMinimumEverFreeHeapSize()                     :  283140
heap_caps_get_free_size(MALLOC_CAP_EXEC)              :  202212
heap_caps_get_free_size(MALLOC_CAP_32BIT)             :  362208
heap_caps_get_free_size(MALLOC_CAP_8BIT)              :  288812
heap_caps_get_free_size(MALLOC_CAP_DMA)               :  288812
heap_caps_get_free_size(MALLOC_CAP_PID2)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID3)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID3)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID4)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID5)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID6)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID7)              :       0
heap_caps_get_free_size(MALLOC_CAP_SPIRAM)            :       0
heap_caps_get_free_size(MALLOC_CAP_INTERNAL)          :  362208
heap_caps_get_free_size(MALLOC_CAP_DEFAULT)           :  288812
heap_caps_get_free_size(MALLOC_CAP_INVALID)           :       0
heap_caps_get_largest_free_block(MALLOC_CAP_EXEC)     :  113792
heap_caps_get_largest_free_block(MALLOC_CAP_32BIT)    :  125024
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)     :  125024
heap_caps_get_largest_free_block(MALLOC_CAP_DMA)      :  125024
heap_caps_get_largest_free_block(MALLOC_CAP_PID2)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID3)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID3)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID4)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID5)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID6)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID7)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM)   :       0
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) :  125024
heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT)  :  125024
heap_caps_get_largest_free_block(MALLOC_CAP_INVALID)  :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC)      :  202192
heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT)     :  356516
heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)      :  283140
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA)       :  283140
heap_caps_get_minimum_free_size(MALLOC_CAP_PID2)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID3)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID3)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID4)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID5)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID6)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID7)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)    :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL)  :  356516
heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT)   :  283140
heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID)   :       0

上記が一般的なESP32ボードです。esp_get_free_heap_size()とxPortGetFreeHeapSize()が同じ数値を返しているので、同じ用途だと思われます。espがつくのがESP-IDFの関数で、xPortがつくのがFreeRTOSの関数ですね。どちらを使ってもよいと思いますが、汎用的なのはFreeRTOSの関数かもしれません。

メモリは確保して開放するを繰り返すと、空いている領域が飛び飛びになります。そのため実際の残メモリと、連続した一番広いメモリ領域は一致しません。freeが付いている関数は空き容量の合計で、minimumが付いている関数は実際には使えないだろうと思われる領域を除いた保守的な空き容量です。largestがついている関数は、一度に確保できる連続したメモリ領域のサイズです。

===============================================================
Mem Test
===============================================================
esp_get_free_heap_size()                              : 4483188
esp_get_minimum_free_heap_size()                      : 4477056
xPortGetFreeHeapSize()                                : 4483188
xPortGetMinimumEverFreeHeapSize()                     : 4477056
heap_caps_get_free_size(MALLOC_CAP_EXEC)              :  192880
heap_caps_get_free_size(MALLOC_CAP_32BIT)             : 4547252
heap_caps_get_free_size(MALLOC_CAP_8BIT)              : 4483188
heap_caps_get_free_size(MALLOC_CAP_DMA)               :  288936
heap_caps_get_free_size(MALLOC_CAP_PID2)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID3)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID3)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID4)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID5)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID6)              :       0
heap_caps_get_free_size(MALLOC_CAP_PID7)              :       0
heap_caps_get_free_size(MALLOC_CAP_SPIRAM)            : 4194252
heap_caps_get_free_size(MALLOC_CAP_INTERNAL)          :  353000
heap_caps_get_free_size(MALLOC_CAP_DEFAULT)           : 4483188
heap_caps_get_free_size(MALLOC_CAP_INVALID)           :       0
heap_caps_get_largest_free_block(MALLOC_CAP_EXEC)     :  113792
heap_caps_get_largest_free_block(MALLOC_CAP_32BIT)    : 4194252
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)     : 4194252
heap_caps_get_largest_free_block(MALLOC_CAP_DMA)      :  124968
heap_caps_get_largest_free_block(MALLOC_CAP_PID2)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID3)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID3)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID4)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID5)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID6)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_PID7)     :       0
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM)   : 4194252
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) :  124968
heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT)  : 4194252
heap_caps_get_largest_free_block(MALLOC_CAP_INVALID)  :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_EXEC)      :  192860
heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT)     : 4541100
heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)      : 4477056
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA)       :  282804
heap_caps_get_minimum_free_size(MALLOC_CAP_PID2)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID3)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID3)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID4)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID5)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID6)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_PID7)      :       0
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM)    : 4194252
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL)  :  346848
heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT)   : 4477056
heap_caps_get_minimum_free_size(MALLOC_CAP_INVALID)   :       0

PSRAMを搭載しているボードでの実験結果が上記です。PSRAMを搭載していると、空き容量が一桁違います。

数値をみていると、一番少なくて重要そうなのがDMA対応メモリです。PSRAMを利用できないので一番小さいサイズになっています。freeは288936ですが、largestが124968となっています。これはESP32のメモリ空間自体が複数に分割されているため、DMAで利用できるメモリは1個目が124968までとなります。分割してもう1個ぐらい確保できそうですが、DMA転送は連続しているメモリ領域である必要があるので利点が半減してしまいます。大きなサイズのDMA転送を行う場合には、他の用途で使われる前に、まずはDMA転送用のメモリを確保する必要がありそうですね。

DEFAULTが通常にメモリ確保したときの残容量です。数値をみてみるとDMA対応メモリとPSRAMメモリの合計になっています。32BITアライメントメモリは標準では利用しないようです。

まとめ

ざっくりとですが、ESP32のヒープメモリの管理がわかってきました。今後実際にメモリの確保をしながらどう変化するのか調べてみたいと思います。

コメントする

メールアドレスが公開されることはありません。

管理者承認後にページに追加されます。公開されたくない相談はその旨本文に記載するかTwitterなどでDM投げてください。またスパム対策として、日本語が含まれない投稿は無視されますのでご注意ください。