OS Xの「現在非使用中」のメモリを開放する

自分のマックはメモリが 4GB の MacBook Pro。ちなみにこれがMacデビュー機。VMWareを2個以上起動するとMacの動きもVMWareの動きもカクカクカクカクで使い物にならなくなる。三万円くらいの8GBメモリがネットに有ったので換装してみた。Micron製のMT16JSS51264HY-1G1A1。ノーブランドの物はもうちょい安かったけど、なんとなくブランド名を冠してるやつにした。


!!!!!!!!!
驚きのサクサクサクサク具合。
VMWareを4個くらい、WinとかLinuxとか動かしても、窓の切り替えとか、窓一覧表示へのアニメーション、アプリの操作、一切引っ掛かりが無い。快適すぎてニヤニヤしてしまった。

でも困ったことが一つ。
ディープスリープ(ハイバネート)からの復帰に時々失敗する・・・。
特にメモリの使用量が多いときに復帰に時間がかかった挙句に失敗するようだ。

アクティビティモニタから確認できる物理メモリの使用状況には次のような状態が有る。

空き(フリー)
文字通り空き
固定中(ワイアード)
使用中で、HDDには移動出きないデータ
現在使用中(アクティブ)
使用中で、最近使用されたデータ
現在非使用中(インアクティブ)
使われていないけど、もしまた必要になったときに高速にアクセスできるように物理メモリ上に残して有るデータ。

インアクティブなデータは、定かではないけど、おそらくHDDからロードしたアプリケーションのプロセスイメージとかデータとかをキャッシュしているのだと思う。

普段はそれほどメモリを使うわけではないけど、半日くらいPCで作業しているとフリーな領域がほとんど無くなってしまうくらいインアクティブな領域が成長している。OSがちゃんと管理してくれてこうなっているわけで、何も問題はないはず。いっぱい積んであるメモリをこのように快適動作のために有効利用してくれうのは非常にありがたい。

ところが、ディープスリープするときにはどうもインアクティブなデータまでHDDに保存しているらしい。復帰時に8GBも読み出すのは待ち時間が長すぎて嫌だ。散々待たされた挙句に失敗と言われるととても悲しい。

このインアクティブな領域、スリープ前に捨ててやる!!! と言うのが今回のネタ。

そういう目的には、メモリクリーナー、メモリ最適化、キャッシュクリア、等と呼ばれるようなソフトが有るらしい。
Mac用で評判が良さそうだったのは「iFreeMem」というシェアウェア

早速DLして試してみた。インターフェイスはメニューバーに常駐してそれをクリックして「メモリ最適化」ボタンを押すだけ。なかなかわかりやすくて、使い易い。
でもインアクティブな部分がまだまだいっぱい残ってる。1GB以上残ってる。

iFreeMemの動作中のメモリグラフを見ていると一瞬メモリ使用量がグンと上がっていた。・・・メモリの確保&開放しているだけ??(笑)

と言うわけで最近勉強中の C でチョロっと書いてみた。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

static const uint32_t ALLOC_UNIT = 4096; // bytes
static const uint32_t NUM_MAX_UNIT = 2097152; // = 2**21
static const uint32_t DEFAULT_MAX_EXECUTION = 30; // sec
static const uint32_t DEFAULT_SWAP_THRESHOLD = 300; // micro-sec

uint32_t clock_to_microsec(clock_t clock) {
    return (uint32_t)((double) clock / CLOCKS_PER_SEC * 1000000);
}

void print_usage() {
    printf("usage: bigmem [-t <sec>] [-s <micro-sec>]\n");
    printf("    -t : max execution time in second.\n");
    printf("         default value is 30sec.\n");
    printf("\n");
    printf("    -s : threshold time of swap in micro second.\n");
    printf("         exit if 4KB allocating takes more than this.\n");
    printf("         default value is 300 micro-sec.\n");
}

int main(int argc, char *argv[]) {
    clock_t alloc_start, alloc_end;
    time_t prog_start;
    int i, swap_threshold, max_execution, elapsed;
    char **pages, opt;

    pages = NULL;
    max_execution = 0;
    swap_threshold = 0;

    while((opt=getopt(argc,argv,"hs:t:")) != -1){
        switch(opt){
        case 't':
            if ((max_execution = strtol(optarg, NULL, 10)) == 0) {
                print_usage();
                goto end;
            }
            max_execution;

            break;
        case 's':
            if ((swap_threshold = strtol(optarg, NULL, 10)) == 0) {
                print_usage();
                goto end;
            }

            break;
        case 'h': // help
        case ':': // value not specified
        case '?': // unknown option
            print_usage();
            goto end;
        }
    }

    if (swap_threshold == 0) {
        swap_threshold = DEFAULT_SWAP_THRESHOLD;
    }

    if (max_execution == 0) {
        max_execution = DEFAULT_MAX_EXECUTION;
    }

    printf("max execution time: %d [sec]\n", max_execution);
    printf("threshold time of swap: %d [micro-sec]\n", swap_threshold);

    prog_start = time(NULL);

    pages = (char **) calloc(sizeof(char *) * NUM_MAX_UNIT, 1);
    if (pages == NULL) {
        printf("memory allocation error.\n");
        goto end;
    }

    for (i=0; i < NUM_MAX_UNIT; i++) {
        alloc_start = clock();

        pages[i] = (char *) malloc(ALLOC_UNIT);
        if (pages[i] == NULL) {
            printf("memory allocation error.\n");
            goto end;
        }

        memset(pages[i], 1, ALLOC_UNIT);

        alloc_end = clock();

        if (clock_to_microsec(alloc_end - alloc_start) > swap_threshold) {
            printf("threshold time of swap exceeded. exitting...\n");
            printf("alloc and memset %d bytes took: %d [micro-sec]\n",
                   ALLOC_UNIT, clock_to_microsec(alloc_end - alloc_start));
            break;
        }

        if (time(NULL) - prog_start > max_execution) {
            printf("max execution time exceeded. exitting...\n");
            break;
        }

    }

    printf("allocated memory %u [bytes]\n", ALLOC_UNIT * i);

 end:
    if (pages != NULL) {
        printf("freeing memory...\n");

        for (i = 0; i < NUM_MAX_UNIT; i++) {
            if (pages[i] == NULL) {
                break;
            }

            free(pages[i]);
        }

        free(pages);
        printf("Done\n");
    }

    return 0;
}

動作

  • 4KBずつ8GBまでメモリを確保しようとする
  • 4KBのメモリ確保に -s オプションで指定した閾値以上の時間がかかっている場合はスワップが発生(=物理メモリ使い果たし)とみなして終了する
  • -t オプションで指定した実行時間が経過するとメモリの確保状況に関わらず終了する


コンパイル

$ gcc -o bigmem bigmem.c

最初にこのファイル名で書き始めてしまったもので(^^;


使い方

usage: bigmem [-t ] [-s ]
    -t : max execution time in second.
         default value is 30sec.

    -s : threshold time of swap in micro second.
         exit if 4KB allocating takes more than this.
         default value is 300 micro-sec.


実行前


実行中
じょじょにアクティブな領域が増えてこうなる


実行後


1分くらい実行後


オプションの値を色々試してたところ、スワップが発生しているからと言ってインアクティブ領域が即座に開放されるわけではないらしい。-s オプションを大きめの値に設定して -t で実行時間を60秒位に設定しておくと、インアクティブ領域がほぼ破棄されるようだ。

空きメモリと4KBのメモリの確保&memsetにかかる時間の関係

空きメモリ 時間(μs)
4
4
5
4
5
4
4
× 589
× 663
× 711
× 378
× 527
× 335
× 343

アクティビティモニタのグラフから空きメモリが無くなった辺りから100倍以上遅くなった。このデータに基づき -s のデフォルトは 300μs としている。私の環境に合わせて。
しかし上でも書いたように、空きメモリが無くなったからと言って即終了したのではインアクティブな領域は全然なくなってくれない。この値は -s 1000 くらいにして置いて、実行時間を -t 60 などとしておいた方が効果的だった。

まとめ

  • インアクティブ領域を開放して、ディープスリープからの復帰でエラーにならず程々の時間で復帰が完了できたので、当初の目的は果たさせた
  • 空きメモリ使い果たすだけではインアクティブ領域は消えてくれない
  • 数GBのスワップファイルができるくらいメモリを確保し続けるとやっとインアクティブ領域は消えてくれる
  • bigmem自身の動作によるインアクティブ領域はなぜできないのか・・・
  • 余談: RAMBackというアドオンがFireFoxのメモリ開放に役立ってくれた 最初の一度だけ役に立った・・・