C++ でグローバル配列または静的配列を定義する場合、そのメモリはプログラムの開始時にすぐに予約されるのではなく、配列に書き込むと一度だけ予約されます。私が驚いたのは、配列のごく一部に書き込むだけでは、メモリ全体を確保できないことです。グローバル配列にまばらに書き込む次の小さな例を考えてみましょう。
#include <cstdio>
#include <cstdlib>
#define MAX_SIZE 250000000
double global[MAX_SIZE];
int main(int argc, char** argv) {
if(argc<2) {
printf("usage: %s <step size>\n", argv[0]);
exit(EXIT_FAILURE);
}
size_t step_size=atoi(argv[1]);
for(size_t i=0; i<MAX_SIZE; i+=step_size) {
global[i]=(double) i;
}
printf("finished\n"); getchar();
return EXIT_SUCCESS;
}
さまざまなステップ サイズに対してこれを実行し、たとえば次のような top の出力を確認します。
./a.out 1000000
./a.out 100000
./a.out 10000
./a.out 1000
./a.out 100
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
15718 user 20 0 1918m 1868 728 S 0 0.0 0:00.00 a.out
15748 user 20 0 1918m 10m 728 S 0 0.1 0:00.00 a.out
15749 user 20 0 1918m 98m 728 S 1 0.8 0:00.04 a.out
15750 user 20 0 1918m 977m 728 S 0 8.1 0:00.39 a.out
15751 user 20 0 1918m 1.9g 728 S 23 15.9 0:00.80 a.out
RES 列は、メモリが小さなブロックでのみ予約されていることを示しています。これは、配列が物理メモリ内で連続している可能性が低いことも意味します。物事のより低いレベルについてより多くの洞察を得た人はいますか?
これには、RES の合計が以下である限り、すべての VIRT の合計が物理メモリを超える多くのプログラムを簡単に実行できるというマイナスの副作用もあります。ただし、それらがすべてグローバル配列に書き込まれるとすぐに、システムは物理メモリを使い果たし、プログラムに sigkill または何かが送信されます。
理想的には、最初にグローバル変数と静的変数のメモリを予約するようにコンパイラに指示したいと思います。可能?
編集
@Magnus: 行は実際には正しい順序になっています。:) たとえば、最初の行./a.out 1000000
は、配列の 100 万番目のエントリごとに書き込みを行っていることを意味するため、合計で 250 しかありません。これは、わずか 1868k の RES に相当します。最後の例./a.out 100
では、100 エントリごとに書き込まれ、合計メモリも RES=VIRT=1.9g に物理的に割り当てられます。数値から、エントリが配列に書き込まれるたびに、完全な 4k ブロックのようなものが物理メモリに予約されているように見えます。
@Nawaz:配列は仮想アドレス空間で連続していますが、私が理解しているように、OSは怠惰で、実際に必要なときにのみ物理メモリを予約する可能性があります。これは一度に配列全体ではなく小さなブロックで行われるため、物理メモリ内で連続していることをどのように保証できますか?
@Nemo: ありがとうございます。実際にa.out
、最初に一時停止してから配列に書き込む複数のインスタンスを呼び出したときに、oom-killer
メッセージが表示されました/var/log/messages
。実際、あなたのコマンドにより、最初からインスタンスがsysctrl
多すぎるのを防ぐことができます。a.out
ありがとう!
Jun 1 17:49:16 localhost kernel: [32590.293421] a.out invoked oom-killer: gfp_mask=0x280da, order=0, oomkilladj=0
Jun 1 17:49:18 localhost kernel: [32592.110033] kded4 invoked oom-killer: gfp_mask=0x201da, order=0, oomkilladj=0
Jun 1 17:49:20 localhost kernel: [32594.718757] firefox invoked oom-killer: gfp_mask=0x201da, order=0, oomkilladj=0
最後の 2 行は少し気になります。:)
@doron: ありがとう、すばらしい説明です。申し訳ありませんが、賛成/選択できません。