Upgrade to Pro — share decks privately, control downloads, hide ads and more …

mrubyc_kaigi03.pdf

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

 mrubyc_kaigi03.pdf

mruby/c kaigi #03 東の発表
mruby/c で実装したメモリ管理モジュールについて

Avatar for higashihirohito

higashihirohito

November 11, 2022

More Decks by higashihirohito

Other Decks in Technology

Transcript

  1. mruby/c kaigi #03 もくじ • メモリ管理の概要 • TLSFアルゴリズム • 高速化

    • RAM利用率改善(使用量削減) • そのつぎとまとめ
  2. mruby/c kaigi #03 メモリ管理 • やっていることは、 – 必要なサイズのメモリを任意のタイミングで確保 – 使い終わった不要なメモリを返す(解放)

    • 一般に malloc / free int main() { char *p1 = malloc( 100 ); // メモリ確保 100 bytes strcpy( p1, "Hello " ); strcat( p1, "mruby/c" ); puts( p1 ); free( p1 ); // メモリ解放 return 0; }
  3. mruby/c kaigi #03 メモリ管理 • Rubyプログラム等、ある種のシステムでは小さいメモリブロッ クを高頻度で確保、解放する特性がある • 組込システムの libc

    は、全体メモリサイズの観点から、高頻度 の malloc/free には向かない実装が多い(と聞いた) • 独自のメモリ管理を実装することに
  4. mruby/c kaigi #03 メモリ管理 • メモリプールをブロックに分け、双方向リンクリストで管理する • Boundary Tagアルゴリズム •

    各ブロックの先頭にヘッダをつけてブロックの属性を管理する 1ブロック USED_BLOCK / FREE_BLOCK メモリプール全体 content flag free prev block size flag tail size used free used free tail = 0 free = 0 size prev block tail = 0 free = 0 size prev block tail = 0 free = 1 size prev block tail = 1 free = 1 size prev block typedef struct USED_BLOCK { unsigned int t : 1;// flag tail unsigned int f : 1;// flag free uint32_t size; uint32_t prev_offset; } USED_BLOCK;
  5. mruby/c kaigi #03 メモリ管理 • 問題はメモリの確保時に空きブロックをどうやって探すか • メモリプールの先頭からリニアサーチする? 1ブロック USED_BLOCK /

    FREE_BLOCK メモリプール全体 content flag free prev block size flag tail size used free used free tail = 0 free = 0 size prev block tail = 0 free = 0 size prev block tail = 0 free = 1 size prev block tail = 1 free = 1 size prev block typedef struct USED_BLOCK { unsigned int t : 1; // flag tail unsigned int f : 1; // flag free uint32_t size; uint32_t prev_offset; } USED_BLOCK;
  6. mruby/c kaigi #03 TLSFアルゴリズム • TLSF (Two-Level Segregated Fit) Dynamic

    storage allocation for real-time embedded systems (M. Masmano, I. Ripoll, and A. Crespo). Real-Time Systems Symposium, Work-in-Progress Session. Cancun, Mexico, December 2003. • 2のべき乗の分類の下に、さらに細かい分類を行うことでメモリ利 用効率を改善する • 最適なサイズのブロックを即座に見つけだせる (とあるが、実際にはGood-FitでありBest-Fitはしない) • どのような状況でも同じ時間で動作し、最悪時間が存在しないので リアルタイムシステムに向いている ※ Wikipediaより引用
  7. mruby/c kaigi #03 TLSF 空きメモリ管理 • 空きメモリは、ヘッダ情報に加え、空きメモリ管理用テーブル を使って管理する • 空きメモリブロックを、そのサイズごとにテーブル登録

    \ SLI FLI \ 0 1 2 … 7 0 * FREE_BLOCK 0000-000F 0010-001F 0020-002F 0070-007F 1 0080-008F 0090-009F 00A0-00AF 00F0-00FF 2 0100-011F 0120-013F 0140-015F 01E0-01FF ・ ・ 9 8000-8FFF 9000-9FFF A000-AFFF F000-FFFF 空きブロックテーブル
  8. mruby/c kaigi #03 メモリ確保時の動作 • メモリ確保時は、要求するサイズを(2進数の)6bit目で分離 して計算する • First Level

    Index, Second Level Index • FLIは、上からの何番目のビットが立っているかを調べる ⇒ 0〜9 • SLIは、FLIの値に応じて左にスライドし、そこの3bitを得る ⇒ 0〜7 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ignore FLI SLI
  9. mruby/c kaigi #03 メモリ確保時の動作 • FLI,SLIの値から空きブロックが検索できる \ SLI FLI \

    0 1 2 … 7 0 * FREE_BLOCK 0000-000F 0010-001F 0020-002F 0070-007F 1 0080-008F 0090-009F 00A0-00AF 00F0-00FF 2 0100-011F 0120-013F 0140-015F 01E0-01FF ・ ・ 9 8000-8FFF 9000-9FFF A000-AFFF F000-FFFF 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 ignore FLI = 0〜9 SLI = 0〜7
  10. mruby/c kaigi #03 高速化 NLZ • FLIは、上位から何番目のビットが立っているかを計算する • 1bitずつシフトしながら15bit目が1になるまでの回数を数える? • もう少し工夫したい。

    15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 bit shift int nlz16( uint16_t x ) { int r; for( r = 0; r < 16; r++ ) { if( (x & 0x8000) == 0x8000 ) break; x <<= 1; } return r; }
  11. mruby/c kaigi #03 高速化 NLZ • 2分法を使って上位から何番目のビットが立っているかを計算 int nlz16( uint16_t x

    ) { if( x == 0 ) return 16; int n = 1; if((x >> 8) == 0 ) { n += 8; x <<= 8; } if((x >> 12) == 0 ) { n += 4; x <<= 4; } if((x >> 14) == 0 ) { n += 2; x <<= 2; } return n - (x >> 15); } • シフト演算による2分法 (バレルシフタ使用) • ゼロ比較 (フラグレジスタに自動的  に反映される) • 小さい数 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
  12. mruby/c kaigi #03 高速化 NLZ 比較 • 素朴な方法 • 高速化 int

    nlz16( uint16_t x ) { int r; for( r = 0; r < 16; r++ ) { if( (x & 0x8000) == 0x8000 ) break; x <<= 1; } return r; } int nlz16( uint16_t x ) { if( x == 0 ) return 16; int n = 1; if((x >> 8) == 0 ) { n += 8; x <<= 8; } if((x >> 12) == 0 ) { n += 4; x <<= 4; } if((x >> 14) == 0 ) { n += 2; x <<= 2; } return n - (x >> 15); } • シフト演算による2分法 (バレルシフタ使用) • ゼロ比較 (フラグレジスタに自動的  に反映される) • 小さい数 • 1bitずつシフト (バレルシフタ未使用) • 0以外の数 0x8000と比較 (比較命令が必要) • 大きな数(0x8000)
  13. mruby/c kaigi #03 利用メモリの拡大に関する改善 1 • 当初最大64KBまでしかサポートしていなかった • 64KB (16bit) 以上のメモリを使いたいという要求

    • size項 16bit 24bit ⇒  ビットフィールドを使って拡大 uint16_t size; uint32_t size : 24; • SCSK九州 三牧氏からのプルリク 後に、ビットフィールドとマルチスレッド(排他制御)との相性の悪さから廃止 content flag tail flag free prev block size 1ブロック USED_BLOCK / FREE_BLOCK unsigned int t : 1; // flag tail unsigned int f : 1; // flag free uint16_t size; uint16_t prev_offset;
  14. mruby/c kaigi #03 利用メモリの拡大に関する改善 2 • TLSFとFirstFitの併用 • 64KBを越えるメモリの確保依頼が来たら、FirstFitアルゴリズ ムに切り替える •

    mruby/cのプログラム特性上、小さなサイズの malloc / free が たくさん発生し、大きいサイズはあまり発生しない • (良い)副作用として、メモリの空きが少なくなっても、残っ た空きメモリを有効に使える可能性が増した
  15. mruby/c kaigi #03 メモリリンクヘッダについての改善 • 全てのメモリブロックについていて、典型的には12バイト • もったいないので、縮小できないか考える typedef struct

    USED_BLOCK { unsigned int t : 1; // flag tail unsigned int f : 1; // flag free (padding 30bit) uint32_t size; uint32_t prev_offset; } USED_BLOCK; used free used free tail = 0 free = 0 size prev block tail = 0 free = 0 size prev block tail = 0 free = 1 size prev block tail = 1 free = 1 size prev block 12bytes 12bytes 12bytes 12bytes 4 bytes 4 bytes 4 bytes
  16. mruby/c kaigi #03 最後尾フラグの廃止 • flag tailは、最後尾ブロックかを判断するフラグ • 最後のブロックが usedからfreeになるときにのみ使用する

    • リンクリストの最後にサイズ0の決して解放されないブロック (番兵)を置けば、flag tail が不要になる content flag tail flag free prev block size typedef struct USED_BLOCK { unsigned int t : 1; // flag tail unsigned int f : 1; // flag free uint32_t size; uint32_t prev_offset; } USED_BLOCK; used free used free tail = 0 free = 0 size prev block tail = 0 free = 0 size prev block tail = 0 free = 1 size prev block tail = 1 free = 1 size prev block
  17. mruby/c kaigi #03 最後尾フラグの廃止 • リンクリストの最後にサイズ0の決して解放されないブロック (番兵)を置けば、flag tail が不要になる •

    サイズは、1bit しか削れない • フラグ操作のコードが不要になる – コードサイズ縮小 – 速度向上 used free used free free = 0 size prev block free = 0 size prev block free = 1 size prev block free = 1 size prev block free = 0 size = 0 prev block typedef struct USED_BLOCK { unsigned int f : 1; // flag free uint32_t size; uint32_t prev_offset; } USED_BLOCK;
  18. mruby/c kaigi #03 前ブロックサイズの追い出し • 前ブロックに関する情報は以下の場合にのみ必要となる – usedブロックの解放時、前ブロックが空きブロックかを判定 – 空きならばブロック同士をマージする

    flag free prev block size free typedef struct USED_BLOCK { unsigned int f : 1; // flag free uint32_t size; uint32_t prev_offset; } USED_BLOCK; flag free prev block size free used flag free prev block size ... ... ... ...
  19. mruby/c kaigi #03 前ブロックサイズの追い出し • 前ブロックが利用中か空きかのフラグをもうけて、prev_block の情報は直前のfreeのエリアに追い出そう – どうせ使ってないエリアなんだし free

    flag free size prev in use used flag free size prev in use prev block typedef struct USED_BLOCK { unsigned int f : 1; // flag free unsigned int p : 1; // prev in use uint32_t size; } USED_BLOCK; ※ glibcからアイデア借用 free flag free prev block size used flag free prev block size
  20. mruby/c kaigi #03 フラグビットはsizeへ含める • ブロックサイズは 4byte align しているので、下 2bit

    は必ず0 • ならば、size の下 2bit をフラグとして使おう used flag free size prev in use typedef struct USED_BLOCK { unsigned int f : 1; // flag free unsigned int p : 1; // prev in use uint32_t size; } USED_BLOCK;
  21. mruby/c kaigi #03 フラグビットはsizeへ含める • ブロックサイズは 4byte align しているので、下 2bit

    は必ず0 • ならば、size の下 2bit をフラグとして使おう used flag free size prev in use used size (f,u) typedef struct USED_BLOCK { uint32_t size; } USED_BLOCK; typedef struct USED_BLOCK { unsigned int f : 1; // flag free unsigned int p : 1; // prev in use uint32_t size; } USED_BLOCK; 31 30 29 28 ... 3 2 1 free 0 use size
  22. mruby/c kaigi #03 解放されることのないメモリブロック • クラス定義などは、定義はあっても undef は無い • つまり確保はあっても解放はされないメモリブロック

    • 解放用の情報そのものが要らなくない? • 12bytes あったヘッダが 0bytesに! used typedef struct USED_BLOCK { } USED_BLOCK;
  23. mruby/c kaigi #03 結果(比較) • 12bytes あったヘッダを 4bytes に減らした •

    一つでは微々たるものだが、大量のメモリブロックが発生する ので馬鹿にできない size (f,u) flag tail flag free prev block size typedef struct USED_BLOCK { unsigned int t : 1; // flag tail unsigned int f : 1; // flag free uint32_t size; uint32_t prev_offset; } USED_BLOCK; typedef struct USED_BLOCK { uint32_t size; } USED_BLOCK;
  24. mruby/c kaigi #03 そのつぎとまとめ • とあるプログラムのメモリ確保と解放のプロ ファイル • 同程度のサイズの確保と解放が連続する •

    解放要求があってもすぐにメモリプールに入れ ずに「いましがた解放キャッシュ」をつくって 取っておくとよいかも • これも既に glibc の malloc がそうなってたよ... alloc(120) alloc(36) alloc(60) alloc(52) alloc(32) free(52) alloc(52) alloc(32) ... alloc(60) alloc(60) alloc(60) free(60) alloc(60) free(60) free(60) alloc(60) free(60) free(60) alloc(60) alloc(60) free(60) ...
  25. mruby/c kaigi #03 そのつぎとまとめ • RealtimeOS 下で複数のVMを動かすケースに対応していない – え? •

    alloc, free のタイミングで Lock かけて動かしてみた – 70%の速度ダウン orz... • 真面目に作るしかないね – HALの拡張がいるね – メモリもふんだんにいるね
  26. mruby/c kaigi #03 そのつぎとまとめ • メモリ管理モジュールを、mruby/c VM の特性に適した独自実装 とすることで、速度向上と使用率向上を達成できた •

    他人の知恵を拝借しまくるだけでも、けっこういける • このモジュールは、VMと密結合しているわけではないので、こ れ (alloc.c) だけで別プロジェクトでも使える – ゲームなどでは、TLSF の alloc を自作する例があると聞いた • もう少し速くなるかも? おしまい