Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
@hiro_kamezawa
アジェンダ 
自己紹介 
cgroupって何? 
今昔 
memory cgroup
自己紹介 
亀澤 寛之(@hiro_kamezawa) 
某F社 
今の仕事はマネージャ 
現役だったのは2年前 
HPCじゃなくエンタープライズ系 
元memory cgroupの開発者 
最近面白そうなもの Dockerと周辺 kdbus、systemd
Cgroup 
“Control Group” の略 
-タスクのグループを作る -作ったグループに対し制限・パラメータ等を設定する ための機能 例えば、KVMのフロントエンドの “libvirt” は各 KVMを cgroupの下に突っ込んで動かすことで CPU,Memory等の 
リソース制御を行っている。
Cgroup ? 
実は元々、”Process Container ” って名前で開発が始まった。 
+/* 
+ * cgroup_create_dir - create a directory for an object. 
+ * cont: the cgroup we create the directory for. 
+ * It must have a valid ->parent field 
+ * And we are going to fill its ->dentry field. 
+ * dentry: dentry of the new container 
+ * mode: mode to set on new directory. 
+ */ 
昔のコードを見ると“Container”の文字が結構あったりする 
“Process Container”機能だけじゃ”Container”にならないよね? 
とかそういう話もあって “Control Group”に
汎用性のために 
Cgroup を作っている最中には色々議論があって 
特定用途でなく“汎用性”を重視すべきだとか。 
それで今の形、cpu, memory etc…をバラバラに管理できるようになった。 名前も Control Groupに。 
Kernel 2.6.24 (2007年)にマージ。 当時からgoogleのエンジニアがメンテナ(代替わりしたけど)。 
=>結局、無駄に複雑にしたことで、後々問題になるんだけど
昔と今と 
なんだこのCgroup機能って、俺たちのコードを汚くする上に 
性能を落とすだけの機能じゃねーか。 
誰が使うんだこんなもん 
昔の雰囲気 
コンテナとかで使ってるみたいだからユースケースが 
あるんだろうが、ホットパスをいじる時は気をつけろよ 
昔よりも根本的なところでコードをよくして行ける。
Cgroupの機能 
タスクをグループ化する 
グループは動的に生成・廃棄が可能 
各グループはヒエラルキを作る 
各ヒエラルキには“サブシステム”で様々な属性を付与 
仮想ファイルシステムインタフェースで実装される 
mkdir で cgroupを作る 
タスクの子タスクは同一のグループに所属する 
全てのタスクはいずれかのcgroupに所属する 
所属していないように見えるタスクは “root” cgroupに所属
現在のsubsystem 
cpu 
cpuacct 
cpuset 
memory 
blkio 
device 
freezer 
net_class 
net_pio 
perf 
hugetlb
ヒエラルキ① 
Cgroupは階層構造が持てる。これをヒエラルキと呼ぶ。 
Subsystem毎に別々のヒエラルキを構築可能 
CPU のヒエラルキ 
メモリのヒエラルキ 
バラバラに作ると 
正直カオス・・・
ヒエラルキ② 
Subsystem毎に階層構造の効果が異なる! 
CPU のヒエラルキ 
メモリのヒエラルキ 
増々カオス・・・ 
基本的に上位のリソース 
を下位で分割してゆく 
1/2 
1/2 
1/2 x 1/3 
上位の影響をうけるかどうか 
スイッチで選べる
ヒエラルキ③ 
A 
B 
C 
D 
中間層にもタスクが所属可能! 
CPU のヒエラルキ 
例えば、Aグループの下に 
B、C,D,グループがあるとする。 
Aグループにタスクが3つ所属し、 
B,C,Dにそれぞれタスクが 
1つずつ所属する時、それぞれ 
のタスクが受け取るCPU 
リソースはいくつか? 
1/2 
1/2 
1/2 x 1/3 
わけわからん
タスクとプロセス 
コントロール⇒タスク単位 
リソース⇒タスク単位? 
メモリってプロセス単位ですよね?
自由度高杉問題 
自由度を優先した実装だったんだけど・・・ 
複雑なコード 
わけのわからんヒエラルキ 
Memory等はプロセス単位で管理されるのにスレッド単位で グループに所属可能 
Cgroup間の連携が皆無 
⇒memory cgroup と blkio cgroupがバラバラになっている ため、buffered I/O にうまくタグ付けが出来ないから buffered I/O制限が実施できないとか。
sane_behavior 
mount -t cgroup -o __DEVEL__sane_behavior cgroup $MOUNT_POINT 
元々の実装がcgroupの使い方に制約を加えていないことへの怒りが生み出した “正しい”使い方 
ヒエラルキ-はシステムに一つだけ(default ヒエラルキ-を作る) 
Remount/rename は不可 
プロセス単位で管理。タスク単位では管理しない 
Taskの所属しないcgroup状態のnotification機能追加 
プロセスを持たないグループだけがサブシステム効果を得る 
Memory, blkio cgroupはデフォルトでヒエラルキー効果が有効化 
カーネル 3.19~20あたりでdefault化かな?
新ヒエラルキルール 
A 
B 
C 
D 
ヒエラルキ 
例えば、Aグループの下に 
B、C,D,グループがあるとする。 
AグループでCPU cgroupをenable 
した場合、BCDに効果が及ぶが Aにはタスクが所属できない。 BCDにのみタスクが所属可能 
1/2 
1/2 
1/2 x 1/3
cpusetの運命? 
cpuset はcgroupが出来る前からあったのだけど、似て るってことでcgroupに取り込まれた。 
元々cpusetはスレッド単位でcpu bindの制御等ができた 
今もできている 
sane_behaviorになると?
systemd 
systemdが全てのcgroupを牛耳る! 
Systemdが有効なシステムではsystemdでcgroupを作る 
Unit file を使って作る 
DBUS APIで作る 
….mkdir よりめんどくさい …けどsystemdのツール群は正直便利 
※ libvirt のcgroup関係もsystemdを使うよう書き直された
3つのslice 
systemd 
user.slice 
system.slice 
machine.slice
ちなみに 
今の所systemd から設定できるパラメータは限定的 
CPUの”Share”は設定できても“Limit”は設定できない 
Memoryの上限は設定できても Swapの設定ができない 
Etc….. 
Systemdの開発者に聞いてところ、Linux Kernel側の 
Cgroupの再設計作業がひと段落してAPIが確定するまで 増やさないとのこと・・・ 
⇒手動でどうぞ。
Cgroupあれこれ-第4回コンテナ型仮想化の情報交換会資料
Memory cgroup 
1.メモリ使用量の上限を設定できる 
ユーザメモリとページ(ファイル)キャッシュ 
2.メモリ+swap使用量の上限を設定できる 
3.カーネルメモリの使用量上限を設定できる 
4.TCPのバッファ量を制限できる 
1~3は連携している。 
….実はTCPのバッファ量制御だけ仲間外れで他の3つとは独立 (元々network bufferのシステム上限制御のcgroup版だから なのだけど)
ところで・・・・ 
Johannes Weiner が 2014/08/08 のコミットで 
書き換えまくったので(綺麗になったし性能もよくなった) 
そのバージョンに基づいてしゃべります。 
※なので今手に入るカーネルとは少し違うかも。 
昔はホットパス等に手を入れるのに反発もあったので 
かなり外付けコード風にmemory cgroupを作ってた。 
最近は市民権?も出て来たのでよりスリム化している最中。
ユーザメモリのアカウンティング ~ページフォルト~ 
プロセス実行 
ページフォルト 
ページフォルトハンドラ 
Memory cgroup 
課金する 
ページフォルトの際に 
アロケートされたメモリ 
に対し課金を行う
ユーザメモリのアカウンティング② ~メモリ解放~ 
munmap() や exit() 
put_page() 
メモリ解放処理 
Memory cgroup 
使用量の返却
ユーザメモリのアカウンティング③ ~swap out処理~ 
メモリ回収処理 
ページを選択 
スワップキャッシュに追加 
unmap 
Swap out 
宙ぶらりん 
フリー 
メモリ使用量を減らす 
スワップアウトしたことを記録 
ユーザメモリはスワップに行った後も 
開放されない場合があるのがいやらしい・・・
ユーザメモリのアカウンティング ~swapin 処理~ 
プロセス実行 
ページフォルト 
ページフォルトハンドラ 
Swapの記録域から 
Memory cgroup情報を 
読み出して課金する 
Swap-in処理 
実はswap inがくせもので、 
SwapCacheにヒットして、既に課金済の 
ページを拾ってくることがある。 
この辺、race conditionとかややこしい 
-> Johannesの改善で少しマシに。
ページキャッシュのアカウンティング 
ページ 獲得 
ページキャッシュに追加 
メモリ使用量UP 
Radix-Tree 
ページキャッシュから削除 
put_page() 時に 
メモリ使用量を返却 
ページ 解放 
最初にページを読んだ人に課金される。 
共有されるページキャッシュの扱いが問題だったが・・・ 
結局コンテナメインなら共有はあんまりかんがえなくていいよね?
Kernelメモリのアカウンティング① 
1.SLAB / SLUB アロケータからページを割り当てた場合 
2.alloc_kmem_pages()を呼ぶ場合 …とはいえ、alloc_kmem_pages()はkmalloc()から 呼ばれる。 基本、kmallocやslab関係で page単位に課金される。
Kernel メモリのアカウンティング② 
今の所、カーネルメモリ課金は free()されたときに減算。 
…..とはいえ、特定のmemory cgroup狙い撃ちでカーネル 
メモリを解放して行くようなルーチンはいまのところまだ無い。 
つまり、memory cgroupを削除してもまだ課金されている状 態….ということもあるわけで、あんまり使わない方がいい。
TCP bufferのアカウンティング 
元々、システム全体でtcp bufferを制限するための仕組みが あり、それを流用。 
Socket の data用のメモリ域をアロケートするところで判定す る。
Memory cgroupの面倒なところ 
タスクのライフサイクルとメモリのライフサイクルが異なる 
莫大な性能オーバヘッドがあると信じられている 
タスクに課金するわけじゃなくてページに課金しているので race conditionが多い 
….少しずつ説明してみます。
ライフサイクル 
元々のcgroupデザイン時の期待値 
Task がいなくなると参照が無くなる⇒削除可能 
メモリcgroup 
Taskがいなくなっても以下が残る 
ページキャッシュ 
カーネルメモリ 
スワップキャッシュ(残ることがある) 
このあたりの処理をするためにリファレンスカウント処理等々を入れた おかげで性能に影響が出たり、race conditionが発生
オーバヘッド 
Kernelは1ページ(4096byte)単位にPage構造体 (64バイト)を割り当てる。Memory cgroupは1ページごと に、16byte使用する。 
=> 1G毎に4Mバイト。 
Cgroup毎にカウンターを一つ持つ。カウンターはスピンロック で保護される。課金情報はココで管理 
Page 情報の管理のためにロック操作が必要だった ⇒ 2014/08 のパッチ群でJohannesが解決。
Race condition 
ページに対して課金する 
ページマイグレーション時の課金の移動 
Transparent Huge Page 
SwapCache 
ページの”dirty”等の属性のカウンティング 
Taskをcgroup間移動した際に課金情報を移動させる機 能がある 
ロックを使っていると性能に出てくる
Memory cgroupの性能オーバヘッド① 
一番最初のナイーブな実装 
1ページの課金ごとにカウンタ(spinlock付)を操作 
/::::i::::、:::ヽ、:::::\:ヽ:\::::::ヽ:::、::ヽ::、:', 
/::i|::l::ト、ヽ::、:::ヽ:、::::::\::ヽ::::l::::ヽ::i:::i:::! 
/:/:!:::!:|::ヽ:\ヽ::::、:\::::ヽ:::ヽ!::::::i::|:::!::! 
!ハ::|::::i::l:|心、:ヽ::\:ヽ_\、\:::ヽ:::|!::|:|i 
i、:!:|:、N{、ヒjヽゝ\ヾイ ヒj >、ヽi:、|!:|:l 
ヽ:!::トヽ ̄ l! ` ` ̄´ |::l::|:|j:,!:! 駄目だこいつ 
ト、::! u j |::/lj:::!リ 
ヾ、 丶 - u リイ:|リ 早くなんとかしないと…… 
リヽ ‐、ー- 、_ /イ:::i 
rー'"ト:l゙、  ̄ ./ , |::! 
/ ヘ ヾ ヽ、 _,. ' / |:'
ヒエラルキとカウンタ 
counter 
counter 
counter 
counter 
lock 
lock 
lock 
lock 
一つカウントすると カウンタとlockを複数操作
性能オーバヘッド改善① 
・・・・・ 
各CPU 
struct { 
struct memcg *ptr; 
unsigned long nr_pages; 
} 
中央のカウンタ 
128kb 単位で課金 
4kb 単位で課金 
ス レ ッ ド 
各CPU毎に128KBずつ、現在走行中のスレッド が所属するmemory cgroupから利用量をキャッシュ。 
(スレッドが切り替わったら必要に応じて返却) 
CPU数が増えたらどうかと思ってたけど自動ヒュージページで影響が見えにくくなった。 
課金処理オーバヘッドの削減 
所属
性能オーバヘッド改善② 
メモリ解放処理等 
Pagevec処理 
Memory cgroupへの返却は put_page() 
のバッチ処理の中で複数のページ分を まとめてカウンタ操作していく。 
今は数百ページ単位で処理。
カウンタの誤差 
Memory cgroup は性能のためにカウント誤差を許容 
-課金の際には各CPUあたり最大128KB分の“前借り” 
-ページテーブルからメモリが剥がされても実際にページアロケー タに返却される直前までは課金が返済されない。 
ファジーなカウンターと呼んだりするけど、Linuxの中にはファジー な値が結構ある。 例)タスク毎のrss accountingとか。
Johannesの改善 
今まで 
lock_page_cgroup() 
pc->memcg = memcg 
unlock_page_cgroup() 
これから 
lock_page_cgroup() 
pc->memcg = memcg 
unlock_page_cgroup() 
どうやったか?
課金処理 
カウンタにチャージ 
Limit Hit ? 
Page単位に記録 
メモリ回収 
Lock 
Lock
try-commit-cancel 
Race conditionを扱うためにmemory cgroupで使われるテクニック 
Try 
リソースを予約 
メモリ関連の処理 
commit 
ページ情報の記録 
成功 
失敗 
cancel
Johannes の改善:lock外し 
改善前 
メモリ獲得 
課金処理 (Try+Commit) 
メモリ管理処理 
改善後 
メモリ獲得 
課金処理(Try) 
メモリ管理処理 
課金処理(Commit) 
課金処理中に処理する必要のあった 競合状態を、処理を2段階に 分割することでメモリ管理処理自身が持つロックの中に隠す 
Lock 
Lock 
Lock 
Lock 
Lock
ページの解放処理改善 
munmap/exit等 
free_pgtables() 
vmaのリストを元に 
Page tableをscan 
zap_pte_range() 
unmapして 
Page tableをクリア 
Page一覧を覚える 
tlb_flush_mmu_free() 
覚えておいたページを解放していく。 最大1万ページ分くらいをまとめて処理。 
1 
実はJohannes のパッチ以前は①の時点で課金を返却していたけど 
以後は②の時点で返却している。つまり、完全に誰も使わなくなってからのみ 
課金の返却を行うようにした。これでLockが外れる。 
2
消えた
ページ回収 
Page 
Active/Inactiveという2つのリストでLRU管理 
補充 
ユーザメモリ 
はActiveから 
ファイル 
はInactive 
Page 
Page 
Page 
Page 
Page 
Active List 
Inactive List 
Accessが無ければ 
Reclaim 
Access Test 
Access 
Test 
Access有
LRUによるページ回収 +memory cgroup 
P 
P 
P 
P 
P 
P 
cgroup 
Active List 
Inactive List 
Memory cgroup毎にLRU(Active/Inactive)を持つ システム全体のLRUは存在しない。
新Global LRU 
P 
P 
P 
P 
P 
P 
cgroup 
P 
P 
P 
P 
P 
P 
cgroup 
P 
P 
P 
P 
P 
P 
cgroup 
P 
P 
P 
P 
P 
P 
cgroup 
現在、システムの 
GLOBAL LRUは 
各memory cgroup 
を巡回してページ回収 
を行っている 
Root cgroupを起点に 
Depth First Search (pre-order) 
でヒエラルキーの木を探索
気になる所 
Q)所謂LRUではないんじゃないの? 
A)LRUではない。 
Q) Scan 順序は? 
A)Node を選択 -> Zoneを選択 -> cgroupを巡回 Q)全cgroupをscan するの? A)基本的には全cgroupを見て回る ->cgroupが多い場合、scan量が増える
Global なメモリ回収と memory cgroup limitのメモリ回収の差 
Globalな回収 
メモリ不足の起きた Node/Zoneからメモリを回収する 
公平性の為とか間違ったOOM判断を避けるために全 cgroupをスキャンする 
Memory Limitによる回収 
Round Robinでnode/zoneからメモリ回収 
ヒエラルキ上関連するcgroupからメモリを回収する 
必要なだけ回収をしたら回収を止める
Vmpressure notify 
“memory.pressure_level” 
メモリ回収動作から“メモリの獲得コスト”を予想する 
Low : メモリ回収は快調 
Medium : ちょっとかかるかも、swapするかも 
Critical : めっちゃ重い、ぶっちゃけ無理 
回収できなかったページ数/スキャンしたページ数 で予測 
512 page回収したら再計算をトリガーする。
今後の強化ポイント(予想) 
Kernel memory cgroupのメモリ回収処理を追加 
Blkio cgroupと連動しての buffered I/Oの制御 
Page付帯情報を16バイトから8バイトへ 
Soft Limit の再実装 
Kswapd per memory cgroup 
…….. 
不揮発メモリの扱い?
Fin.

More Related Content

Cgroupあれこれ-第4回コンテナ型仮想化の情報交換会資料

  • 3. 自己紹介 亀澤 寛之(@hiro_kamezawa) 某F社 今の仕事はマネージャ 現役だったのは2年前 HPCじゃなくエンタープライズ系 元memory cgroupの開発者 最近面白そうなもの Dockerと周辺 kdbus、systemd
  • 4. Cgroup “Control Group” の略 -タスクのグループを作る -作ったグループに対し制限・パラメータ等を設定する ための機能 例えば、KVMのフロントエンドの “libvirt” は各 KVMを cgroupの下に突っ込んで動かすことで CPU,Memory等の リソース制御を行っている。
  • 5. Cgroup ? 実は元々、”Process Container ” って名前で開発が始まった。 +/* + * cgroup_create_dir - create a directory for an object. + * cont: the cgroup we create the directory for. + * It must have a valid ->parent field + * And we are going to fill its ->dentry field. + * dentry: dentry of the new container + * mode: mode to set on new directory. + */ 昔のコードを見ると“Container”の文字が結構あったりする “Process Container”機能だけじゃ”Container”にならないよね? とかそういう話もあって “Control Group”に
  • 6. 汎用性のために Cgroup を作っている最中には色々議論があって 特定用途でなく“汎用性”を重視すべきだとか。 それで今の形、cpu, memory etc…をバラバラに管理できるようになった。 名前も Control Groupに。 Kernel 2.6.24 (2007年)にマージ。 当時からgoogleのエンジニアがメンテナ(代替わりしたけど)。 =>結局、無駄に複雑にしたことで、後々問題になるんだけど
  • 7. 昔と今と なんだこのCgroup機能って、俺たちのコードを汚くする上に 性能を落とすだけの機能じゃねーか。 誰が使うんだこんなもん 昔の雰囲気 コンテナとかで使ってるみたいだからユースケースが あるんだろうが、ホットパスをいじる時は気をつけろよ 昔よりも根本的なところでコードをよくして行ける。
  • 8. Cgroupの機能 タスクをグループ化する グループは動的に生成・廃棄が可能 各グループはヒエラルキを作る 各ヒエラルキには“サブシステム”で様々な属性を付与 仮想ファイルシステムインタフェースで実装される mkdir で cgroupを作る タスクの子タスクは同一のグループに所属する 全てのタスクはいずれかのcgroupに所属する 所属していないように見えるタスクは “root” cgroupに所属
  • 9. 現在のsubsystem cpu cpuacct cpuset memory blkio device freezer net_class net_pio perf hugetlb
  • 11. ヒエラルキ② Subsystem毎に階層構造の効果が異なる! CPU のヒエラルキ メモリのヒエラルキ 増々カオス・・・ 基本的に上位のリソース を下位で分割してゆく 1/2 1/2 1/2 x 1/3 上位の影響をうけるかどうか スイッチで選べる
  • 12. ヒエラルキ③ A B C D 中間層にもタスクが所属可能! CPU のヒエラルキ 例えば、Aグループの下に B、C,D,グループがあるとする。 Aグループにタスクが3つ所属し、 B,C,Dにそれぞれタスクが 1つずつ所属する時、それぞれ のタスクが受け取るCPU リソースはいくつか? 1/2 1/2 1/2 x 1/3 わけわからん
  • 14. 自由度高杉問題 自由度を優先した実装だったんだけど・・・ 複雑なコード わけのわからんヒエラルキ Memory等はプロセス単位で管理されるのにスレッド単位で グループに所属可能 Cgroup間の連携が皆無 ⇒memory cgroup と blkio cgroupがバラバラになっている ため、buffered I/O にうまくタグ付けが出来ないから buffered I/O制限が実施できないとか。
  • 15. sane_behavior mount -t cgroup -o __DEVEL__sane_behavior cgroup $MOUNT_POINT 元々の実装がcgroupの使い方に制約を加えていないことへの怒りが生み出した “正しい”使い方 ヒエラルキ-はシステムに一つだけ(default ヒエラルキ-を作る) Remount/rename は不可 プロセス単位で管理。タスク単位では管理しない Taskの所属しないcgroup状態のnotification機能追加 プロセスを持たないグループだけがサブシステム効果を得る Memory, blkio cgroupはデフォルトでヒエラルキー効果が有効化 カーネル 3.19~20あたりでdefault化かな?
  • 16. 新ヒエラルキルール A B C D ヒエラルキ 例えば、Aグループの下に B、C,D,グループがあるとする。 AグループでCPU cgroupをenable した場合、BCDに効果が及ぶが Aにはタスクが所属できない。 BCDにのみタスクが所属可能 1/2 1/2 1/2 x 1/3
  • 17. cpusetの運命? cpuset はcgroupが出来る前からあったのだけど、似て るってことでcgroupに取り込まれた。 元々cpusetはスレッド単位でcpu bindの制御等ができた 今もできている sane_behaviorになると?
  • 18. systemd systemdが全てのcgroupを牛耳る! Systemdが有効なシステムではsystemdでcgroupを作る Unit file を使って作る DBUS APIで作る ….mkdir よりめんどくさい …けどsystemdのツール群は正直便利 ※ libvirt のcgroup関係もsystemdを使うよう書き直された
  • 19. 3つのslice systemd user.slice system.slice machine.slice
  • 20. ちなみに 今の所systemd から設定できるパラメータは限定的 CPUの”Share”は設定できても“Limit”は設定できない Memoryの上限は設定できても Swapの設定ができない Etc….. Systemdの開発者に聞いてところ、Linux Kernel側の Cgroupの再設計作業がひと段落してAPIが確定するまで 増やさないとのこと・・・ ⇒手動でどうぞ。
  • 22. Memory cgroup 1.メモリ使用量の上限を設定できる ユーザメモリとページ(ファイル)キャッシュ 2.メモリ+swap使用量の上限を設定できる 3.カーネルメモリの使用量上限を設定できる 4.TCPのバッファ量を制限できる 1~3は連携している。 ….実はTCPのバッファ量制御だけ仲間外れで他の3つとは独立 (元々network bufferのシステム上限制御のcgroup版だから なのだけど)
  • 23. ところで・・・・ Johannes Weiner が 2014/08/08 のコミットで 書き換えまくったので(綺麗になったし性能もよくなった) そのバージョンに基づいてしゃべります。 ※なので今手に入るカーネルとは少し違うかも。 昔はホットパス等に手を入れるのに反発もあったので かなり外付けコード風にmemory cgroupを作ってた。 最近は市民権?も出て来たのでよりスリム化している最中。
  • 24. ユーザメモリのアカウンティング ~ページフォルト~ プロセス実行 ページフォルト ページフォルトハンドラ Memory cgroup 課金する ページフォルトの際に アロケートされたメモリ に対し課金を行う
  • 25. ユーザメモリのアカウンティング② ~メモリ解放~ munmap() や exit() put_page() メモリ解放処理 Memory cgroup 使用量の返却
  • 26. ユーザメモリのアカウンティング③ ~swap out処理~ メモリ回収処理 ページを選択 スワップキャッシュに追加 unmap Swap out 宙ぶらりん フリー メモリ使用量を減らす スワップアウトしたことを記録 ユーザメモリはスワップに行った後も 開放されない場合があるのがいやらしい・・・
  • 27. ユーザメモリのアカウンティング ~swapin 処理~ プロセス実行 ページフォルト ページフォルトハンドラ Swapの記録域から Memory cgroup情報を 読み出して課金する Swap-in処理 実はswap inがくせもので、 SwapCacheにヒットして、既に課金済の ページを拾ってくることがある。 この辺、race conditionとかややこしい -> Johannesの改善で少しマシに。
  • 28. ページキャッシュのアカウンティング ページ 獲得 ページキャッシュに追加 メモリ使用量UP Radix-Tree ページキャッシュから削除 put_page() 時に メモリ使用量を返却 ページ 解放 最初にページを読んだ人に課金される。 共有されるページキャッシュの扱いが問題だったが・・・ 結局コンテナメインなら共有はあんまりかんがえなくていいよね?
  • 29. Kernelメモリのアカウンティング① 1.SLAB / SLUB アロケータからページを割り当てた場合 2.alloc_kmem_pages()を呼ぶ場合 …とはいえ、alloc_kmem_pages()はkmalloc()から 呼ばれる。 基本、kmallocやslab関係で page単位に課金される。
  • 30. Kernel メモリのアカウンティング② 今の所、カーネルメモリ課金は free()されたときに減算。 …..とはいえ、特定のmemory cgroup狙い撃ちでカーネル メモリを解放して行くようなルーチンはいまのところまだ無い。 つまり、memory cgroupを削除してもまだ課金されている状 態….ということもあるわけで、あんまり使わない方がいい。
  • 31. TCP bufferのアカウンティング 元々、システム全体でtcp bufferを制限するための仕組みが あり、それを流用。 Socket の data用のメモリ域をアロケートするところで判定す る。
  • 32. Memory cgroupの面倒なところ タスクのライフサイクルとメモリのライフサイクルが異なる 莫大な性能オーバヘッドがあると信じられている タスクに課金するわけじゃなくてページに課金しているので race conditionが多い ….少しずつ説明してみます。
  • 33. ライフサイクル 元々のcgroupデザイン時の期待値 Task がいなくなると参照が無くなる⇒削除可能 メモリcgroup Taskがいなくなっても以下が残る ページキャッシュ カーネルメモリ スワップキャッシュ(残ることがある) このあたりの処理をするためにリファレンスカウント処理等々を入れた おかげで性能に影響が出たり、race conditionが発生
  • 34. オーバヘッド Kernelは1ページ(4096byte)単位にPage構造体 (64バイト)を割り当てる。Memory cgroupは1ページごと に、16byte使用する。 => 1G毎に4Mバイト。 Cgroup毎にカウンターを一つ持つ。カウンターはスピンロック で保護される。課金情報はココで管理 Page 情報の管理のためにロック操作が必要だった ⇒ 2014/08 のパッチ群でJohannesが解決。
  • 35. Race condition ページに対して課金する ページマイグレーション時の課金の移動 Transparent Huge Page SwapCache ページの”dirty”等の属性のカウンティング Taskをcgroup間移動した際に課金情報を移動させる機 能がある ロックを使っていると性能に出てくる
  • 36. Memory cgroupの性能オーバヘッド① 一番最初のナイーブな実装 1ページの課金ごとにカウンタ(spinlock付)を操作 /::::i::::、:::ヽ、:::::\:ヽ:\::::::ヽ:::、::ヽ::、:', /::i|::l::ト、ヽ::、:::ヽ:、::::::\::ヽ::::l::::ヽ::i:::i:::! /:/:!:::!:|::ヽ:\ヽ::::、:\::::ヽ:::ヽ!::::::i::|:::!::! !ハ::|::::i::l:|心、:ヽ::\:ヽ_\、\:::ヽ:::|!::|:|i i、:!:|:、N{、ヒjヽゝ\ヾイ ヒj >、ヽi:、|!:|:l ヽ:!::トヽ ̄ l! ` ` ̄´ |::l::|:|j:,!:! 駄目だこいつ ト、::! u j |::/lj:::!リ ヾ、 丶 - u リイ:|リ 早くなんとかしないと…… リヽ ‐、ー- 、_ /イ:::i rー'"ト:l゙、  ̄ ./ , |::! / ヘ ヾ ヽ、 _,. ' / |:'
  • 37. ヒエラルキとカウンタ counter counter counter counter lock lock lock lock 一つカウントすると カウンタとlockを複数操作
  • 38. 性能オーバヘッド改善① ・・・・・ 各CPU struct { struct memcg *ptr; unsigned long nr_pages; } 中央のカウンタ 128kb 単位で課金 4kb 単位で課金 ス レ ッ ド 各CPU毎に128KBずつ、現在走行中のスレッド が所属するmemory cgroupから利用量をキャッシュ。 (スレッドが切り替わったら必要に応じて返却) CPU数が増えたらどうかと思ってたけど自動ヒュージページで影響が見えにくくなった。 課金処理オーバヘッドの削減 所属
  • 39. 性能オーバヘッド改善② メモリ解放処理等 Pagevec処理 Memory cgroupへの返却は put_page() のバッチ処理の中で複数のページ分を まとめてカウンタ操作していく。 今は数百ページ単位で処理。
  • 40. カウンタの誤差 Memory cgroup は性能のためにカウント誤差を許容 -課金の際には各CPUあたり最大128KB分の“前借り” -ページテーブルからメモリが剥がされても実際にページアロケー タに返却される直前までは課金が返済されない。 ファジーなカウンターと呼んだりするけど、Linuxの中にはファジー な値が結構ある。 例)タスク毎のrss accountingとか。
  • 41. Johannesの改善 今まで lock_page_cgroup() pc->memcg = memcg unlock_page_cgroup() これから lock_page_cgroup() pc->memcg = memcg unlock_page_cgroup() どうやったか?
  • 42. 課金処理 カウンタにチャージ Limit Hit ? Page単位に記録 メモリ回収 Lock Lock
  • 43. try-commit-cancel Race conditionを扱うためにmemory cgroupで使われるテクニック Try リソースを予約 メモリ関連の処理 commit ページ情報の記録 成功 失敗 cancel
  • 44. Johannes の改善:lock外し 改善前 メモリ獲得 課金処理 (Try+Commit) メモリ管理処理 改善後 メモリ獲得 課金処理(Try) メモリ管理処理 課金処理(Commit) 課金処理中に処理する必要のあった 競合状態を、処理を2段階に 分割することでメモリ管理処理自身が持つロックの中に隠す Lock Lock Lock Lock Lock
  • 45. ページの解放処理改善 munmap/exit等 free_pgtables() vmaのリストを元に Page tableをscan zap_pte_range() unmapして Page tableをクリア Page一覧を覚える tlb_flush_mmu_free() 覚えておいたページを解放していく。 最大1万ページ分くらいをまとめて処理。 1 実はJohannes のパッチ以前は①の時点で課金を返却していたけど 以後は②の時点で返却している。つまり、完全に誰も使わなくなってからのみ 課金の返却を行うようにした。これでLockが外れる。 2
  • 47. ページ回収 Page Active/Inactiveという2つのリストでLRU管理 補充 ユーザメモリ はActiveから ファイル はInactive Page Page Page Page Page Active List Inactive List Accessが無ければ Reclaim Access Test Access Test Access有
  • 48. LRUによるページ回収 +memory cgroup P P P P P P cgroup Active List Inactive List Memory cgroup毎にLRU(Active/Inactive)を持つ システム全体のLRUは存在しない。
  • 49. 新Global LRU P P P P P P cgroup P P P P P P cgroup P P P P P P cgroup P P P P P P cgroup 現在、システムの GLOBAL LRUは 各memory cgroup を巡回してページ回収 を行っている Root cgroupを起点に Depth First Search (pre-order) でヒエラルキーの木を探索
  • 50. 気になる所 Q)所謂LRUではないんじゃないの? A)LRUではない。 Q) Scan 順序は? A)Node を選択 -> Zoneを選択 -> cgroupを巡回 Q)全cgroupをscan するの? A)基本的には全cgroupを見て回る ->cgroupが多い場合、scan量が増える
  • 51. Global なメモリ回収と memory cgroup limitのメモリ回収の差 Globalな回収 メモリ不足の起きた Node/Zoneからメモリを回収する 公平性の為とか間違ったOOM判断を避けるために全 cgroupをスキャンする Memory Limitによる回収 Round Robinでnode/zoneからメモリ回収 ヒエラルキ上関連するcgroupからメモリを回収する 必要なだけ回収をしたら回収を止める
  • 52. Vmpressure notify “memory.pressure_level” メモリ回収動作から“メモリの獲得コスト”を予想する Low : メモリ回収は快調 Medium : ちょっとかかるかも、swapするかも Critical : めっちゃ重い、ぶっちゃけ無理 回収できなかったページ数/スキャンしたページ数 で予測 512 page回収したら再計算をトリガーする。
  • 53. 今後の強化ポイント(予想) Kernel memory cgroupのメモリ回収処理を追加 Blkio cgroupと連動しての buffered I/Oの制御 Page付帯情報を16バイトから8バイトへ Soft Limit の再実装 Kswapd per memory cgroup …….. 不揮発メモリの扱い?
  • 54. Fin.