障らぬ仮想メモリに祟りなし
前回はpsコマンドのSZについて調べました。
結局仮想メモリ含む全メモリという説明をしたのですが…じゃあSZが1GB使ってて、RSSが100MBだったら、残り900MBがスワップなのかというとそういうわけでもありません。スワップなんて全くされてない…なんてこともあります。今回はこのカラクリを説明しようと思います。
やること
- 仮想メモリを確保する
- 確保したメモリを0クリアする
- メモリを開放する
ただし、各実施手順直後にps -eo uid,pid,rss,vsz,argsを実施します。なお、事前にswapは使用しない設定にしておきます(今回はswap設定なし)。
$ free
total used free shared buff/cache available
Mem: 4030844 1343484 1189184 61208 1498176 2357696
Swap: 0 0 0
$
プログラム
#include <sys/mman.h>
#include <cstring>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
const size_t length = 1024 * 1024 * 1024;
void* pages = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
try {
if (pages == MAP_FAILED) {
throw "エラー";
}
cout << "仮想メモリ確保" << endl;
string s;
getline(cin, s);
memset(pages, 0, length);
cout << "0クリア" << endl;
getline(cin, s);
if (munmap(pages, length) != 0) {
throw "エラー";
}
cout << "開放" << endl;
getline(cin, s);
}
catch(const char* s) {
perror("hoge");
if (pages != MAP_FAILED) {
munmap(pages, length);
}
return 1;
}
return 0;
}
何のことはないプログラムです。Linuxでページメモリを確保するにはmmapを使います。Cの標準関数mallocとかが内部で使ってるシステムコールです。
実行
$ make hoge
面倒なので暗黙ルールだけでビルドしちゃいます。あとはhogeを実行するだけです。
$ ./hoge
仮想メモリ確保
こんな状態になったら、確保だけしてEnter入力待ちしてるので、別端末からpsします。
$ ps -eo uid,pid,rss,vsz,args | grep hoge
1000 121674 1516 1054452 ./hoge
1000 121690 732 10084 grep --color=auto hoge
$
元の端末でEnterを押すと…
$ ./hoge
仮想メモリ確保
0クリア
0クリアされました。また別端末からpsします。
$ ps -eo uid,pid,rss,vsz,args | grep hoge
1000 121674 1051520 1054452 ./hoge
1000 121711 728 10084 grep --color=auto hoge
$
さらに元端末でEnterを押すと…
$ ./hoge
仮想メモリ確保
0クリア
開放
開放されました。また別端末からpsします。
$ ps -eo uid,pid,rss,vsz,args | grep hoge
1000 121674 3076 5876 ./hoge
1000 121735 728 10084 grep --color=auto hoge
$
さて、これで何が分かるのでしょう?
分かること
rss(実メモリ) | vsz(仮想メモリ) | |
---|---|---|
仮想メモリ確保 | 1516 | 1054452 |
0クリア | 1051520 | 1054452 |
開放 | 3076 | 5876 |
仮想メモリの0クリア時に、rss(実メモリ)が1GBほど増えています。そして仮想メモリの開放と同時にrss(実メモリ)が1GBほど減ってます。これはどういうことかというと、(mmapでMAP_ANONYMOUSを指定して確保した)「仮想メモリは使用すると実メモリが割り当てられる」ということです。
未使用の仮想メモリはそのアドレスにアクセスできるというだけで、実体がありません。アクセスされたときにはじめてページ(4KB)という単位で実体が割り当てられます。ここで実体とは実メモリのことなので、rss(実メモリ)が増えるという仕組みなのです。その際に実メモリが足りない場合、あまり使用されていない実メモリがスワップに書き込まれ、空いた実メモリがこのアドレスに割り当てられます。スワップが足りない場合は、セグメンテーション違反が発生します。メモリ確保時でもないのにそんな例外が発生することにまず驚愕すると思いますが、そういう仕組みなので仕方ありません。気になる場合は0クリアすればいいだけですしね。
何はともあれ、仮想メモリは確保しただけで未使用なら何も圧迫しないということが分かったと思います。
malloc(C++のnew)ならどうなるか?
今回はmallocを内部で使うC++のnew演算子(デフォルト)で実験です。
#include <cstring>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
const size_t length = 1024 * 1024 * 1024;
char* pages = nullptr;
new char[length];
try {
pages = new char[length];
cout << "メモリ確保" << endl;
string s;
getline(cin, s);
memset(pages, 0, length);
cout << "0クリア" << endl;
getline(cin, s);
delete[] pages;
pages = nullptr;
cout << "開放" << endl;
getline(cin, s);
}
catch(const char* s) {
perror("hoge");
if (pages != nullptr) {
delete[] pages;
}
return 1;
}
return 0;
}
今回はこれを使います。手順は同じなので、いきなり結果だけ。
rss(実メモリ) | vsz(仮想メモリ) | |
---|---|---|
メモリ確保 | 1520 | 2103036 |
0クリア | 1051588 | 2103036 |
開放 | 3156 | 1054456 |
実メモリの動きはC++のnewでも同じですね。newで確保したサイズの倍程度の仮想メモリが用意されていますが、それ以外は想定どおり「確保したメモリは使用すると実メモリが割り当てられ」ています。
仮想メモリは不要?
実際にアドレスを確保しただけならほとんどメモリを圧迫しないということなので、なら実メモリだけが必要で、仮想メモリという指標は要らないのでは?と思うかもしれません。しかし待ってください。今回は意図的に外してますが、実際にはスワップもあるわけです。
swap on
まずは
https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04-ja
を読んでスワップ使ってみましょう。
$ sudo fallocate -l 4G /swapfile
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ free
total used free shared buff/cache available
Mem: 4030844 1337404 1187296 61720 1506144 2363220
Swap: 4194300 0 4194300
$
永続化は今回不要なのでしません。
4GBバージョン
hogeの4GBバージョンをhoge3として作成します。length変えるだけなので、ソースは再掲しません。
これを実行して一時停止したところでpsコマンドを打つわけですが、今回はさらにcat /proc/[pid]/statusも見て、有用そうなパラメータを抜き出しました。結果がコレ。
rss(実メモリ) | vsz(仮想メモリ) | VmSwap | VmHWM | |
---|---|---|---|---|
メモリ確保 | 1528 | 4200180 | 0 | 1528 |
0クリア | 3119744 | 4200180 | 1087448 | 3386592 |
開放 | 620 | 5876 | 120 | 3386592 |
VmSwapがないと使用メモリが良く分かりませんね。しかしこれpsでは表示できません。取得はしてるみたいなんですけど、出力するオプション指定がないのです。
VmHWMはVmHighWaterMarkです。最高水位の記録ですね。実際に(実メモリを)使用した仮想メモリのサイズのMax値を記録してるようです。
VmHWM: peak resident set size (“high water mark”)
https://www.kernel.org/doc/html/latest/filesystems/proc.html
結論
RSSが正義でVSZは微妙ですね。mallocが倍くらいページ確保しちゃうようだし、未使用分が常にあり、スワップが分からない以上、混乱しかしそうにないです。
まとめ
仮想メモリは使用されるまで実体がなく、使用時に実メモリが随時4KBずつ割り当てられる。ので注意。
続き的な記事を書きました。
ディスカッション
コメント一覧
まだ、コメントがありません