間違いだらけのメモリーの話
レベル等
当記事のレベル
私が個人的にプログラマーが一般教養として知っておいて欲しいと考える事柄と水準にしています。
とはいえ、プログラマーと言っても、筆者にはいわゆる汎用機系のノウハウが全くないので、いわゆる Unix ライク OS の存在のもと動作するプログラムを書く人に限定します。(リアルタイム系、制御用マイコン等の OS レスな組込み系も)
想定する読者
- Lightweight Language (Ruby, Python, etc.) で日々オブジェクトを作っては捨てている(と思い込んでいる)人
- Kubernetes でメモリ配分に頭を悩ませている人
スラブアロケーターとか TLB とか THP とか言っている人は、本記事よりもっと専門的なものを読むことをお勧めします。あるいはマサカリの投擲場としてどうぞ。
前提知識の確認
- データを取り扱う ≒ 対象のデータをメモリーに保持
- そもそもプログラム自体、起動時にメモリー上へ読み込まれる
- メモリーは OS に依頼して調達(確保)する
ページ
作業に1バイト必要なので OS から1バイトメモリーを確保したとすると、消費メモリーは1バイトだけ増える?
作業に1バイト必要なので OS から1バイトメモリーを確保したとすると、消費メモリーは1バイトだけ増える?
色々なレイヤーで間違い。
- そもそも、 OS は「ページ」と言う単位でメモリーを管理している。
- 通常のアプリケーションプログラムは、 OS から直接メモリーを確保しない。(詳しくはメモリーアロケーターの項で)
ページ?
こんにち広く使われている OS と CPU は、バイト単位のような細かな単位でメモリーを管理するのではなく、ページと言う単位でメモリーを管理しています。
だいたいのケースで 4KiB です。それ以外のサイズを採用しているアーキテクチャーもあるらしいが、実例を聞いたことはないです。(ヒュージページのお話は別の機会に…)
デマンドページング
OS から直接メモリーを確保してみた。消費メモリーは直ちに増える?
OS から直接メモリーを確保してみた。消費メモリーは直ちに増える?
晴れて OS から自由に使って良いメモリー領域をもらっても、実は本当にその領域にメモリーがあるとは限りません。
あくまで、 OS は「以降そのメモリー領域にアクセスしたらそこにメモリーがあることを保証」しているに過ぎず、実際に書くなり読むなりすることで初めて、その領域に実際のメモリーがあてがわれます。
これをデマンドページングと呼びます。
実際にメモリーがどれだけあてがわれているか?
Linux でプロセスのリアルタイムな統計情報を眺めるときによく使われる top
コマンド。
メモリーに関する列は VIRT
, RES
, SHR
の3つです。 (%MEM
は割合なので除く)
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 uniquev+ 20 0 254196 120812 3116 S 0.0 5.9 0:30.94 ruby
9 uniquev+ 20 0 325912 110024 2264 S 0.0 5.4 0:23.27 ruby
21 uniquev+ 20 0 23148 9292 4532 R 0.0 0.5 0:00.25 top
このうち、 VIRT
が「以降そのメモリー領域にアクセスしたらそこにメモリーがあることを保証」されている量、 RES
が「実際のメモリーがあてがわれている」量になります。 (SHR
についてはまたの機会に)
VIRT
は VIRTual (なんで仮想なのかは次の項で)、 RES
は RESident (常駐)の略で、 RES
はコマンドや文献などによって RSS (Resident Set Size: 常駐セットサイズ) とか rsz (Resident SiZe) とも。
論理メモリーと物理メモリー
プログラム内で取り扱ったメモリーのアドレスが例えば 12345 だったとして、それはハードウェアに実装されているメモリーの 12345 バイト目を指している?
プログラム内で取り扱ったメモリーのアドレスが例えば 12345 だったとして、それはハードウェアに実装されているメモリーの 12345 バイト目を指している?
前項まででそれとなくほのめかしていましたが、そもそもアプリケーションプログラムが取り扱っているメモリー領域は、ハードウェア的な意味でのメモリーそのものとは直接何の関係もありません。
アプリケーションは論理的なメモリー領域を扱っていればそれでよく、物理的にどのアドレスのメモリーが使用されることになっているかは OS の裁量でうまいことやる、と言うスタンスです。
前者を論理メモリー(or 論理アドレス or 論理ページ), 後者を物理メモリー(or 物理アドレス or 物理ページ)と言います。
スワッピングないしページング
頭の良い人なら、「論理メモリーと物理メモリーに直接の関係がないのなら、論理メモリーにあてがわれている物理メモリーを一時的に剥奪してストレージに保存するなりで内容を保全した上で別のプログラムに融通できるのでは?」と考えるかもしれません。
スワッピングないしページング
まさにその通りで、それを具現化したのが「スワッピング」、あるいは今風な用語では「ページング」と呼ばれる機構です。(名前のとおりっぷりは何故か前者の方が良い様子)
このスワッピングが起こると、物理メモリーを剥奪される側(スワップアウトorページアウト)は見かけ上 RSS が減少します。
逆に、かつて物理メモリーを剥奪された側が再び問題のメモリー領域をアクセスしようとすると、 OS によってそれが検知されて、別の物理メモリーが都合され、元々の内容がストレージから読み込まれた上で処理が続行されます。(スワップインorページイン)