メモリはmallocしたタイミングで確保されるのか

初Qiita投稿です。

#調査動機
あるマルチスレッドプログラムの実行時間が想定よりも遅かったので
どこがボトルネックか調査したら、memsetでのゼロクリアが原因だった。
memsetにそんなに時間がかかるのか？と思ったので軽く調査しました

#結論
memsetがボトルネックになっていたのは
メモリが確保されるのがmalloc時ではなく
**初めての書き込み時(今回だとmemsetでゼロクリア)だったため**
書き込み時に実際の物理メモリの確保を行うため重い処理に見えてしまった。

#調査内容
mallocを実行するとヒープ、もしくはページからメモリがmalloc時に確保されるというのが自分の認識でした。
実際はmalloc時に仮想メモリを引数で指定した分だけ**予約**するのが正しかったみたいです。

書き込み時にメモリを確保している様子を実際に見てみます。

[調査環境]
AWSのサービスであるEC2を使用しました（t2.micro）
OS:Amazon Linux AMI
CPU:Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
メモリ：1GB

mallocとメモリ書き込みを行うソースコード
20MB分malloc後に、1秒ごとに1MBずつmemsetでゼロクリアしています

```c:malloc_page.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

#define TOTAL 20
#define PAGE_SIZE 4096
#define PER_MB 1 * 1024 * 1024
int main()
{
  char *arr[TOTAL]={NULL};
  int cnt_malloc=0;

  //20MB memory alloc
  for (int i=0; i < TOTAL; i++)
  {
    arr[i] = (char *)malloc(PER_MB);
    if (arr[i] == NULL)
    {
      printf("can't alloc\n");
      break;
    }
    printf("memory address %d:%p\n", i, arr[i]);
    cnt_malloc++;
}

  sleep(3);

  //write memory
  for (int i=0;i < cnt_malloc;i++)
  {
     //１秒ごとに書き込み
     memset(arr[i], 0, PER_MB);
     printf("write memory %p\n", arr[i]);
     sleep(1);
  }

  for (int i=0;i < TOTAL;i++)
  {
    free(arr[i]);
  }

  return 0;
}

```

vmstatを使用してメモリの使用量を監視します。
以下がそのスクリプトファイルです。

```bash
#!/bin/bash

#vmstatで１秒ごとのメモリ使用量監視
vmstat -S M 1 > page.log 2>&1 &
PID=$!
sleep 3

#mallocとmemsetを行う
./malloc_page
sleep 3
kill $PID

exit 0

```


以下に結果を示します。

```
[ec2-user@ip-10-10-1-233 ~]$ cat page.log
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0    208     52    572    0    0    29   126   26   48  1  0 99  0  0
 0  0      0    208     52    572    0    0     0     0   12   18  0  0 100  0  0
 0  0      0    208     52    572    0    0     0    40   30   71  1  0 99  0  0
ここでmalloc
 1  0      0    208     52    572    0    0     0     0   43   94  1  1 98  0  0
 0  0      0    208     52    572    0    0     0     0   29   62  0  0 100  0  0
 0  0      0    208     52    572    0    0     0     0   13   24  0  0 100  0  0
3秒スリープ
 1  0      0    208     52    572    0    0     0     0    9   26  0  0 100  0  0
１秒ごとに１MBのmemset開始。合計２０MB
 0  0      0    207     52    572    0    0     0    28   26   72  0  0 100  0  0
 0  0      0    206     52    572    0    0     0     0   10   17  0  0 100  0  0
 0  0      0    205     52    572    0    0     0     0   11   18  0  0 100  0  0
 0  0      0    203     52    572    0    0     0     0   39   86  1  0 99  0  0
 0  0      0    203     52    572    0    0     0    52   43  120  1  0 99  0  0
 0  0      0    203     52    572    0    0     0     0   12   19  0  0 100  0  0
 0  0      0    202     52    572    0    0     0     0   10   17  0  0 100  0  0
 0  0      0    200     52    572    0    0     0     0    9   19  0  0 100  0  0
 0  0      0    199     52    572    0    0     0     0   15   30  0  0 100  0  0
 0  0      0    199     52    572    0    0     0     0   10   18  0  0 100  0  0
 0  0      0    197     52    572    0    0     0    12   12   22  0  0 100  0  0
 0  0      0    195     52    572    0    0     0     0   36   75  1  0 99  0  0
 0  0      0    195     52    572    0    0     0     0   37   96  0  0 100  0  0
 0  0      0    195     52    572    0    0     0    16   16   27  1  0 99  0  0
 0  0      0    194     52    572    0    0     0     0   12   18  0  0 100  0  0
 0  0      0    192     52    572    0    0     0     0    9   18  0  0 100  0  0
 0  0      0    191     52    572    0    0     0    40   11   23  0  0 99  1  0
 0  0      0    191     52    572    0    0     0     0   10   18  0  0 100  0  0
 0  0      0    189     52    572    0    0     0     0   16   26  0  0 100  0  0
 0  0      0    187     52    572    0    0     0     0   35   79  0  1 99  0  0
20MBのmemset終了。free関数で解放。
 0  0      0    208     52    572    0    0     0     0   39  102  0  0 100  0  0
 0  0      0    208     52    572    0    0     0     0    7   12  0  0 100  0  0
 0  0      0    208     52    572    0    0     0    24    8   15  0  0 100  0  0

```

vstatで出力された結果の4列目freeの項目に注目してください。
20MBのmalloc終了3秒後からmemsetで書き込みを始めています。
そこからメモリが1秒ごとに1MBずつ使用されているのが分かります。

結果を見るに、malloc時には仮想メモリのX番地からXバイト分予約だけ行い、
実際に書き込む際に初めて物理メモリにマッピングされるのだと思います。
今回の問題が発生したプログラムはマルチスレッド実行でしたが、物理メモリを確保する処理はカーネルがシリアルに、排他的に処理を行うため（この情報が正しいかどうかはあまり自信がないです）、余計にmemsetの際の実行時間が長く見えてしまった。

今回はmallocでページサイズ分だけメモリを割り当てる際のみ検証を行いました。
ヒープ領域から割り当てる場合にどのような動きをするのかは分かりません。
ちなみにmallocするサイズを128KB以下にするとヒープ領域からメモリを割り当てられます。
ヒープ領域からメモリを割り当てるかどうかは割り当てサイズに依存します。デフォルトの閾値は128KB、128KB以下ならヒープ領域を使用します。
環境変数M_MMAP_THRESHOLDの値を変えることで閾値を調整可能です。


（調査してモヤモヤは晴れましたが、この知識が何かに使えるかと言われれば微妙）