Slide 1

Slide 1 text

1 mallocの旅(glibc編) kosaki@ぬまづ

Slide 2

Slide 2 text

2 今日は何の話? • libcでもっとも良く使われる関数、mallocと freeの実装の解説 • もっと一般的に言うと、プロセスのアドレス空 間のうち、heap領域とよばれる、場所を操作 する関数の説明 • 解説というと聞こえはいいが、そんな大層なも のじゃない

Slide 3

Slide 3 text

3 Linux での process address space model kernel stack text mmap data bss heap 矢印はデータ量の増加と ともに、伸びる方向 使用中 使用中 使用中 今日は、ここ、heapと呼ばれる領域のお話 low high free free free

Slide 4

Slide 4 text

4 古典的malloc プログラミング言語C(いわゆるK&R)で紹介された初期のUnixのmalloc実装 使用中 使用中 使用中 free listの head 使用中 ・free listを使って空きメモリを管理 ・プロセス全体でただ1つのHeapを使う ・mallocするときに管理領域分だけ多くallocateし て先頭に管理領域を付加 (どこかに管理領域がないとfreeするときに開放 sizeがわからない) ・割り付けstrategy はfirst fit. union header{ struct{ union header* ptr; unsigned size; }s; long alignment; };

Slide 5

Slide 5 text

5 mallocのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 このぐらい 欲しい 足りない 1.まず、list headから先頭ポインタをget 2.空き領域が十分か調べる ・・・・小さすぎた

Slide 6

Slide 6 text

6 mallocのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 また足りない このぐらい 欲しい 1.まず、list headから先頭ポインタをget 2.空き領域が十分か調べる 3.ポインタをlistの次の要素に進める 4.また空き領域が十分か調べる うむむ。。また小さい

Slide 7

Slide 7 text

7 mallocのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 1.まず、list headから先頭ポインタをget 2.空き領域が十分か調べる 3.ポインタをlistの次の要素に進める 4.また空き領域が十分か調べる 5.また、ポインタを次の要素に進める 6.またまた、空き領域を調べる 今度はあった!! OK このぐらい 欲しい

Slide 8

Slide 8 text

8 mallocのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 1.まず、list headから先頭ポインタをget 2.空き領域が十分か調べる 3.ポインタをlistの次の要素に進める 4.また空き領域が十分か調べる 5.また、ポインタを次の要素に進める 6.またまた、空き領域を調べる 7.空き領域を2つに分けて、 free listをつなぎなおす 8.list head を更新 今確保した 領域 最後に探索が失敗した場所 (アロケートされたメモリの1つ前の要素) を指すように変更

Slide 9

Slide 9 text

9 実は・・・ 使用中 使用中 使用中 free listの head 使用中 実はもう1つ先を探すと、もっといい場所が あったのに・・・ このぐらい 欲しい 今確保した 領域

Slide 10

Slide 10 text

10 freeのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 freeしたい 領域 1.free list headから最初のポインタをゲット 2.最初の要素の、さらに次のポインタもゲット (next = p->s.ptr) 3.p < bp < next が成立しないので次へ bp p next

Slide 11

Slide 11 text

11 freeのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 freeしたい 領域 bp p next 1.free list headから最初のポインタをゲット 2.最初の要素の、さらに次のポインタもゲット (next = p->s.ptr) 3.p < bp < next が成立しないので次へ 4.p を次の要素に進める 5.次のポインタをゲット 6.p < bp < next が成立した

Slide 12

Slide 12 text

12 freeのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 bp p 7.p とbpは隣接していない (p + p->s.size != bp) ので併合しない next 1.free list headから最初のポインタをゲット 2.最初の要素の、さらに次のポインタもゲット (next = p->s.ptr) 3.p < bp < next が成立しないので次へ 4.p を次の要素に進める 5.次のポインタをゲット 6.p < bp < next が成立した 8. bpとp->s.ptrは隣接しているので (bp + bp->s.size == next) 併合 freeしたい 領域

Slide 13

Slide 13 text

13 freeのアルゴリズム 使用中 使用中 使用中 free listの head 使用中 bp p 7.p とbpは隣接していない (p + p->s.size != bp) ので併合しない next 1.free list headから最初のポインタをゲット 2.最初の要素の、さらに次のポインタもゲット (next = p->s.ptr) 3.p < bp < next が成立しないので次へ 4.p を次の要素に進める 5.次のポインタをゲット 6.p < bp < next が成立した 8. bpとp->s.ptrは隣接しているので (bp + bp->s.size == next) 併合 9. free list head を今開放した要素を 指すよう動かす

Slide 14

Slide 14 text

14 次にmallocの特殊なケース heapにまったく空きがなくて heap自体を拡張するケースを 説明します

Slide 15

Slide 15 text

15 ヒープ拡張 使用中 free listの head 使用中 このぐらい 欲しい heapは無限ではないのでいつか足りなくなる 足りない

Slide 16

Slide 16 text

16 ヒープ拡張 使用中 free listの head 使用中 このぐらい 欲しい heapは無限ではないのでいつか足りなくなる 足りない

Slide 17

Slide 17 text

17 ヒープ拡張 使用中 free listの head 使用中 このぐらい 欲しい heapは無限ではないのでいつか足りなくなる 足りない

Slide 18

Slide 18 text

18 ヒープ拡張 使用中 free listの head 使用中 このぐらい 欲しい heapは無限ではないのでいつか足りなくなる ptrとfree listのheadが再び一致 (一周してしまった)

Slide 19

Slide 19 text

19 ヒープ拡張 使用中 free listの head 使用中 このぐらい 欲しい heapは無限ではないのでいつか足りなくなる ここでbrkシステムコールでheap領域を一気に 伸ばす brkはheap最後尾アドレスを変更するAPI heap最後尾 heap最後尾

Slide 20

Slide 20 text

20 ヒープ拡張 使用中 使用中 heapは無限ではないのでいつか足りなくなる heap最後尾 今確保した 領域 増えた領域を2つにわけ、先頭をユーザに返却。 残りをfree listにつなぐ free listの head

Slide 21

Slide 21 text

21 やや脱線

Slide 22

Slide 22 text

22 素朴なbrkの実装 0. データセグメントには静的データとスタック が入っている 1. 現在値+増加分で (kernel 内で) malloc 2. 新しいメモリにメモリコピー 3. 古いデータを mfree 4 スタックを末尾にメモリコピー 5. スタックと静的データの間をゼロクリア data stack data stack data stack new memory data stack data stack data data stack stack 出典: Lions’ Commentary on UNIX 増加分

Slide 23

Slide 23 text

23 つまり • カーネルbrkがくそ遅い • あんまりbrk呼ばなくてもいいように、ユー ザー空間で「なるべくbrkしない。するときはガ バッと一気に取る」方針でいく • とかいう価値観で実装されておりまする

Slide 24

Slide 24 text

24 さて • 実はbrkも、イマドキのLinuxでは大幅に高速 になっているのだが、それは今回は考えない 事にする • 今はbrkが遅いって前提でmallocの高速化を 考えていくぜい 脱線終わり

Slide 25

Slide 25 text

25 K&R mallocのいいところ • 単純 • コードサイズが小さい。 (組み込みとかもmallocはいまだにこんな形 しとるよ) • フラグメンテーションが進まない限りmallocは O(1) • プログラム全体で数十回しかmallocしないよ うな小規模プログラムではとてもうまく動く

Slide 26

Slide 26 text

26 K&R mallocのダメなところ • 小さいmallocが多発するとフラグメンテーショ ンがすぐ進む • freeがO(n) • brkが呼ばれる状況では一回freelistを一周す る必要がある (リストが数万個もあれば、なにそのキャッ シュ・フラッシング・コード状態) • フラグメンテーションが進むとメモリ効率も急 激に悪化

Slide 27

Slide 27 text

27 時代は変わった・・・ • イマドキなプログラミング – GUI – スクリプト言語やJava – C++プログラミング – 等々 は、まさに小さいmallocが連発される

Slide 28

Slide 28 text

28 最大の問題はなんだろう? • ここは、とりあえずフラグメンテーションが最 大の問題だと仮定しよう • フラグメンテーションさえ解決すれば – メモリ使用効率UP! – 使用メモリ量が減れば、それだけキャッシュに載 る確率UP! – なんか カコ(・∀・)イイ!

Slide 29

Slide 29 text

29 とゆーわけで、時代は best fitアロケータなのである

Slide 30

Slide 30 text

30 で、Just Ideaに従って 実装してみる

Slide 31

Slide 31 text

31 アドレス順をやめて、 サイズ順にソートしてみる 使用中 使用中 使用中 free listの head 使用中 freeの時に、隣接要素と併合することが不可能に・・・・ よけいフラグメンテーションが進みました 本末転倒

Slide 32

Slide 32 text

32 やっぱりmalloc headerに メンバを増やすしかない struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk* fd; struct malloc_chunk* bk; }; 使用中 使用中 使用中 free listの head 使用中 変数名をglibcにあわせたので、だいぶ雰囲気が 変わったけど気にしない address spaceのprev, nextはポインタで 持たずサイズで保持している。

Slide 33

Slide 33 text

33 何が変わったのか • 良くなったところ – freeがtypicalでO(n)からO(1)へ – フラグメンテーションによる空間の無駄が減る • 悪くなったところ – mallocがtypicalでO(1)からO(n)へ – ヘッダサイズが増えて空間効率ダウン

Slide 34

Slide 34 text

34 だめだめです むしろ此処からが本題

Slide 35

Slide 35 text

35 ヘッダのダイエットが必要です • まず、free listのポインタ、bk, fdは割り付け 済みブロックには必要ない • これは単純に削ればいい • アクセス方法には注意 prev_size size fd bk malloc_chunk構造体にキャストして アクセスするので一見、fd, bkメンバが あるように見えるが、実はそこは ユーザアプリに使われてしまっているので アクセスするとメモリ破壊 ソースコードからは読み取りにくい・・

Slide 36

Slide 36 text

36 ダイエットはつづくよ・・・ • よーく考えると • prev_sizeメンバはfree時の合併処理のみに必要 • とゆーことは、prevがfree状態のときのみ必要 • prev_sizeはprevがfreeの時のみ記録したい ちょっとまって • どうやって、prevがfree状態か調べればいいんだっけ? (卵と鶏問題)

Slide 37

Slide 37 text

37 32bitなシステムのポインタって・・ 0 31 1 2 ポインタ 0 0 • 下位2bitは絶対0になるよね • glibc mallocは実際には内部で8の倍数に切 り上げるから、下位3bitは0 • sizeメンバは2つのポインタの差を記録してい るのだから、当然、同じく下位3bitが0

Slide 38

Slide 38 text

38 というわけで prev_size size fd bk size fd bk use free free prev_size size use size 1 0 sizeメンバの再下位bitをprevがUSE状態かを 記録するビットとして使う (図中の が最下位ビットを示している) 再下位が0ならprev_sizeメンバがある。 1 1 free()関数で chunk_p = (malloc_chunk*)(((char*)ptr) - sizeof(size_t)*2); なんてやってるけど、sizeメンバ以外はあるかどうか 分からない 構造体の型とメモリ上のデータ構造がまるで 一致していない香ばしい構造 → 読みにくさの主原因 ブロック1 ブロック2 ブロック3 ブロック4

Slide 39

Slide 39 text

39 時系列で見ると prev_size size fd bk use free free prev_size size fd bk 0 1 1 prev_size size fd bk use free free prev_size size fd bk 0 1 prev_size size fd bk malloc 1 1 prev_size size fd bk prev_size size fd bk 余分に確保するメモリは 4バイトのみ。 request2size() が req + sizeof( malloc_chunk) ではなく req + sizeof(size_t) なのは ここに原因があった!! malloc編 mallocヘッダ mallocボディ(使用中) mallocボディ(free) 当然だけど、mallocヘッダから 突き抜けているmalloc_chunk メンバはアクセスしたらエライ事になります fd,bkメンバはユーザに使われて しまうので壊される

Slide 40

Slide 40 text

40 時系列で見ると use free 1 1 1 1 prev_size size fd bk prev_size size fd bk use prev_size size fd bk prev_size size fd bk free free 1 1 0 1 prev_size size fd bk prev_size size fd bk use prev_size size fd bk prev_size size fd bk freeの時に初めて fd, bk, prev_sizeメンバが 書き込まれる sizeメンバ以外は、 malloc時には確保してなかったのだが どうせfreeじゃーん。 あいてるじゃーん。 という訳で勝手に使ってる。 ソース上はとってもメモリ破壊ちっく free free編 ここでprev_in_useフラグが1に

Slide 41

Slide 41 text

41 ダイエットは出来たので • ある意味、本日のcode readingの最難関部 分は突破(^-^; (他の部分は、ちゃんとC言語ちっくなCの ソースコード(?)なので) • 次は最大の課題。 malloc() がtypical でO(n)じゃーん。問題を片 付ける

Slide 42

Slide 42 text

42 ここでアイデア • 別にfree listで、1つのリストに全部つながなく てもいいよね? • サイズは絶対8の倍数なんだから、サイズ16 用のリスト、サイズ24用のリスト・・・・ ってやったらbest fist かつO(1)じゃね?

Slide 43

Slide 43 text

43 small bin 16 24 32 40 504 ・・・ size index 2 63 3 4 5 chunks これで小さいサイズのmallocが /* 8の倍数に切り上げ */ size = request2size(req); if( size <= 512 ) { bin_index = size/8; chunk = bins[bin_index].bk; unlink(chunk); /* remove freelist */ return chunk + sizeof(size_t)*2; } このぐらい簡単に終わる 構造体とかはたいてい、このぐらいのサイズにおさまるよね? best fit どころか、just fit アロケータですよ。と 8 8 8 8 8 bin width free list headの配列

Slide 44

Slide 44 text

44 さらに改良 • 512byte overの部分が手付かず • でも、大きいサイズにも8byteおきにbinを用 意するのは現実的じゃない • でもリストを複数もつ。 というアイデアは悪くない

Slide 45

Slide 45 text

45 large bin 588 652 716 780 ・・・ size index 65 123 66 67 68 64 64 64 64 32K bin width 124 32K 125 250K 126 250k 127 ∞ グラフにするとこんなカンジ bin width bin index bin indexが大きくなるにつれ、あつかうblock sizeの幅が指数的に大きくなるように調整 小さいサイズのほうが数が多いので、リストにつながる数を平均化するための施策 大きなサイズ用の リストヘッドも64個つくる ・・・ 512 4k ・・・ 750k more

Slide 46

Slide 46 text

46 でも • どう頑張っても、一番ラストのbinは一杯つな がってしまうんだよね • 画像とか扱うと平気で数十Mとかmallocする し・・・・ • あーあ、Heapをもう1つ用意できたら でかいメモリを完全に分離できるのに・・

Slide 47

Slide 47 text

47 その為のmmapです

Slide 48

Slide 48 text

48 anonymous mmapとは? • mmapは、本来ファイルをメモリにマップする システムコール • でも fd 引数に “/dev/zero” を渡すことにより、 メモリ確保APIとして使用可能 • このAPIを使って、Huge Block(デフォルトで 128K byte以上)はheapからではなく、mmap で直接kernelから取得する

Slide 49

Slide 49 text

49 またしてもsizeメンバの下位bitを 0 31 1 2 size 0 0 0 IS_MMAPED PREV_IN_USE 下から2bit目をmmapから取得したよーん。 という意味で使うことにする。 このbitがONならfree listからではなくMMAPで取得 しているので、free時にfreelistにつながずに、 いきなり munmap()する

Slide 50

Slide 50 text

50 データ構造図解 使用中 使用中 使用中 使用中 使用中(huge) 使用中(huge) bins ※1 見やすくするために、リストをつなぐ線の種類を少しずつ変えてある ※2 Hugeかつfree状態はありえない。開放と同時にOSに返却するから

Slide 51

Slide 51 text

51 この方法の利点 • Huge Blockもmalloc, freeともにO(1) • フラグメンテーション、むっさ起きにくい (リスト管理してないんだから当たり前) • メモリの無駄が少ない (でっかいメモリは同じサイズで再度mallocさ れる確率は低いので、すぐさまOSに返却す るのは賢い戦略)

Slide 52

Slide 52 text

52 ここまでの結果 • 良くなったところ – mallocがtypical で O(1) – freeがtypicalでO(1) – フラグメンテーションがすごく起きにくく – ヘッダサイズは実質4バイト – brkが発生するときに、K&R mallocではfreelistを一周す る必要があったのが、要求サイズより大きいbinを検索す るだけでよくなった。 平均で探索コスト1/2 • 悪くなったところ – なんかあったっけ?

Slide 53

Slide 53 text

53 でも! それでも! しばしば、K&R mallocに 負けるんです。これが 実はlarge size blockの malloc – free –malloc – free と 繰り返す割り当てが遅い

Slide 54

Slide 54 text

54 K&R freeを振り返ってみる 使用中 使用中 使用中 free listの head 使用中 bp p next 9. free list head を今開放した要素を 指すよう動かす 今開放した 要素 ここがポイント

Slide 55

Slide 55 text

55 キャッシュと局所参照性 • heapメモリーに一番アクセスする確率が高い のはmalloc直後とfree直前である • freeされたばかりのメモリはキャッシュに載っ てる確率が高い • そこから優先してメモリ確保することはmalloc 直後のアクセスでキャッシュミスしなくなるとい うこと キャッシュのヒット率重要

Slide 56

Slide 56 text

56 バッファの遅延合体 • freeが呼ばれたときに、すぐに隣と併合&free listに つなぐ処理をやめる • 最初にこのアイデアを実装したのはSVR4らしい(最 前線UNIXのカーネル より) • malloc – free – malloc – free という非常によくある アクセスパターンでメモリブロックの分離・併合・分 離・併合という無駄な処理が避けられる。 • かつ、freeされた順に時系列にリストにつながって いるので、リスト先頭のblockをアプリに返せば キャッシュヒット率向上

Slide 57

Slide 57 text

57 バッファの遅延合体 その2 • gligc mallocでは最低確保サイズが32なので bins[0]とbins[1]は使ってない • bins[1] をこの遅延されてるblockをつなげるリストの リストヘッドとして特別な意味で用いる • ソースコード上はunsorted_chunkと呼ばれている が、ソートしない=時系列順である。 • リストをたぐって、要求サイズと一致するものを検索 • 要求サイズと一致しないものは、この時点で、隣と 併合して実際のfree処理

Slide 58

Slide 58 text

58 マクロな視点で話をすると • mallocの呼び出しパターンはたいてい以下のような 経過をへる • アプリ起動時はやたらmallocが呼ばれる。freeはほ とんど呼ばれない • その後、mallocとfreeがほぼ交互に呼ばれる定常 状態に入る • GUIの画面遷移のような、なんからの契機で、free がひたすら呼ばれ、次にmallocがひたすら呼ばれる データ構造の大転換がおこる • そしてまた定常状態に・・・・

Slide 59

Slide 59 text

59 mallocの 定常状態とバースト状態 バースト状態 バースト状態 このとき、遅延併合が 裏目に出る。 遅延併合リストに要素が 一杯たまるから メ モ リ 使 用 量 定常状態 遅延併合は裏目にでることもあるが、いちばんありがちな、 定常状態で高速化されるのでモトがとれる

Slide 60

Slide 60 text

60 まだもうちょっとだけ 続くんじゃ

Slide 61

Slide 61 text

61 みんな大好きマルチスレッド 猫まっしぐら!

Slide 62

Slide 62 text

62 素朴なlock malloc(size_t sz){ lock(); ptr = internal_malloc(sz); unlock(); return ptr; } ご冗談でしょう。ファインマンさん たんじゅんに、関数全体を mutexで保護してみた

Slide 63

Slide 63 text

63 本当はこうしたい 使用中 使用中 使用中 使用中 使用中 使用中 使用中 使用中 bins 使用中 使用中 使用中 使用中 bins bins スレッド1 スレッド2 スレッド3 スレッド1専用heap スレッド2専用heap スレッド3専用heap ロックのいらない素敵な世界

Slide 64

Slide 64 text

64 それは流石に無理 • アプリがいくつスレッドをつくるか事前に知る 方法はない • 1つのスレッドが最大どのくらいのメモリを使 うのか事前に知る方法はない • ITRONだと両方ともコンパイル時に決まるの に・・

Slide 65

Slide 65 text

65 そこで以下のように実行時に新 しいheapを作っていく

Slide 66

Slide 66 text

66 Arena生成 使用中 使用中 使用中 使用中 bins スレッド1 main_arena main_arena == 今まで説明してきたheap アクセス ロック arena 構造体

Slide 67

Slide 67 text

67 使用中 使用中 使用中 使用中 スレッド1 main_arena アクセス ロック アクセス、しかし、ロックとれず bins arena 構造体 スレッド2 別のスレッドがmallocを同時に呼ぶと、 ロック取得(mutex_trylock)に失敗 Arena生成 スレッド2

Slide 68

Slide 68 text

68 Arena生成 スレッド2 使用中 使用中 使用中 使用中 スレッド1 main_arena アクセス ロック スレッド2 新しい自分専用heapをmmapで作成 このaltanative heap の仕組みを arenaと呼んでいる。 TLS(thread local strage)に自分用arenaを覚えておくので スレッドが増えるか1M使い切るかしない限り二度とバッティングしない bins arena 構造体 とってきたメモリの先頭を arena構造体 (bin配列などが入っている構造体)として使う 1M free mmap arena同士はlist でつなげる

Slide 69

Slide 69 text

69 Arena生成 スレッド3 使用中 使用中 使用中 使用中 スレッド1 main_arena スレッド2 bins arena 構造体 free アクセス スレッド3 アクセス 次のスレッドも、ロック競合が起きるまではmain_arenaを 使い続ける

Slide 70

Slide 70 text

70 Arena生成 スレッド3 使用中 使用中 使用中 使用中 スレッド1 main_arena アクセス ロック スレッド2 bins arena 構造体 free アクセス スレッド3 アクセス ロックがぶつかったら・・・・

Slide 71

Slide 71 text

71 Arena生成 スレッド3 使用中 使用中 使用中 使用中 スレッド1 main_arena アクセス ロック スレッド2 bins arena 構造体 free アクセス スレッド3 アクセス arena listを、たぐって次々とロック取得 をチャレンジ すべて失敗したら、また新しい自分専 用arenaを作る。

Slide 72

Slide 72 text

72 Arena生成 スレッド3 使用中 使用中 使用中 使用中 スレッド1 main_arena アクセス ロック スレッド2 bins arena 構造体 free アクセス スレッド3 mmap いきなりarena生成をしないのはスレッド2が すでに終了していたときに、その専用arenaが 無駄になるのを防ぐため これにより、スレッド生成直後は色々なarenaで ロック競合するが そのうちに、1スレッド・1アリーナに収束する free

Slide 73

Slide 73 text

73 1スレッド:1Arenaの隠れた利点 • SMPマシンでは、別のCPUからアクセスしたメモリは自分 CPUのキャッシュには乗らないのでラストアクセスを単純に 管理してはうまくいかない • しかし、ユーザ空間から自分がどのCPUで動いているのか 明に意識するのは無理 (いつのまにか勝手に変わるし) • そこでカーネルがもつスレッドのCPU affinityスケジューリン グに着目して、自分スレッドがアクセスしたデータは自分 CPUでアクセスした確率が高いと考える • スレッド専用メモリ=キャッシュヒット率がものすごくUp!

Slide 74

Slide 74 text

74 ところで freeするときに、自分の所属するarenaってどう やって見つけるんだっけ?

Slide 75

Slide 75 text

75 だめアイデア1 • TLSからarenaを取得 → 自分専用arenaを作る前に 何回かmain_arenaから取得 している分がある。 それは、main_arenaに戻さないと。

Slide 76

Slide 76 text

76 だめアイデア2 • それぞれのmalloc headerにarenaへのポイ ンタを追加する → なんのために死ぬ思いでヘッダを 4バイトまで削ったと思ってるんです?

Slide 77

Slide 77 text

77 だめアイデア3 • main_arena(唯一のグローバル変数)から arenaのリストをたどって・・・ → O(n)の検索はダメだっつってんだろ!

Slide 78

Slide 78 text

78 結局どうしたか? • arenaを絶対1M alignされるようにメモリを確 保する。 すると、 ptr & ~0xFFFFF するだけでarena へのポインタが得られるようにする

Slide 79

Slide 79 text

79 課題1 • main_arenaはグローバル変数なんだけ ど・・・ 0 31 1 2 size 0 0 0 IS_MMAPED PREV_IN_USE → 毎度おなじみsizeメンバハックのお時間でございま~す♪ IS_NON_MAINARENA

Slide 80

Slide 80 text

80 課題2 • Linuxに1M alignを保障するメモリ確保システ ムコールってないんだけど → 以下のちょっとトリッキーな方法で可能 (次ページ参照)

Slide 81

Slide 81 text

81 Arenaで1Mにそろえる方法 図解 0x100000 0x200000 こういうメモリ確保がしたい しかしmmapでは出来ない。 low high use free free 1Mアライン

Slide 82

Slide 82 text

82 Arenaで1Mにそろえる方法 図解 0x100000 0x200000 2倍のサイズでmmap ただしPROT_NONE 0x100000 0x200000 余分な場所をmunmap READもWRITEもEXECも不可なメモリ確保というのは メモリを確保しないが、アドレススペースは確保するというのと同義 1Mアラインされた場所からsize 1Mで PROT_READ | PROT_WRITE | PROT_EXEC で remap. 0x100000 0x200000

Slide 83

Slide 83 text

83 まとめ • 小さいmallocは回数がすごくたくさん呼ばれ るので、O(n)ではダメ • フラグメンテーションを防ぐにはHuge Block はHeapを分けるのが効果的 • キャッシュヒット率を上げるには、参照局所性 超重要 • per Threadなデータ構造はper CPUなデータ 構造のよい近似値

Slide 84

Slide 84 text

84 glibc mallocのダメなところ • Huge Blockが絶対page alignされてしまうので、 キャッシュが競合しやすい (HPC分野ではこの機能はOFFにするのが一般的) • もう一工夫すれば、Arenaへのロック自体なくせる (これが問題になるようなheavy allocationアプリは 自前heap管理をしてるので効果は見えにくいかも) • 最新のdlmallocはlarge-binの管理がリストからバイ ナリツリーに変更されて高速化が図られている(でも 使用率がいまいち低いので効果は微妙か?)

Slide 85

Slide 85 text

85 終わりに変えて • glibc mallocは、今日のスライド90枚にわたる色々 なアイデアがint_malloc()という1つの関数にごった 煮で詰め込んであるので、すごく読みにくい • おまいら、ソースコードのコメントにうそを書くなと小 一時間・・ • おまいら、関数分割ぐらいしろと小二時間・・・ • おまいら、構造体の型とメモリ上のデータ構造は合 わせておけと小三時間・・・ • これを見るとLinux kernelってなんて読みやすいの かと・・・・

Slide 86

Slide 86 text

86 ご清聴ありがとうございました! つかれた~ (≧ω≦)ゞ