- (a) まず標準関数のmalloc/freeを使った、以下のプログラムの実行時間を測定しました。「1万回のmalloc&free」を1万回繰り返したときの時間を測っています。最初に10回ほど余計に回していますが、その間は時間を測りません。こうすることで初期のあまり速くない処理時間を混ぜずにトップスピードを測っています。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int i, j, **a = malloc(10000 * sizeof (int *));
clock_t t0 = 0, t1;
for (j = -10; j < 10000; j++) {
if (j == 0) t0 = clock();
for (i = 0; i < 10000; i++)
a[i] = malloc(256 * sizeof (int));
for (i = 0; i < 10000; i++)
free(a[i]);
}
t1 = clock();
printf("time: %.3f[sec]\n", (double) (t1 - t0) / CLOCKS_PER_SEC);
return 0;
}
- シンプルすぎてツッコミどころがない感じですが、 Core i7-2600 3.40GHz で測定したところ35.188秒でした。まあそんなものだろうなという感じです。
- (b) 次にKMallocを使ってみます。大きな違いは、freeするときにもサイズを指定しなければいけないところでしょうか。KMallocではメモリ管理のメタデータをあえて持たないので、freeするときにもサイズを教えてやらなければいけないのです。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "kclib1.h"
int main()
{
int i, j, **a = malloc(10000 * sizeof (int *));
clock_t t0 = 0, t1;
KAutoReleasePool_init();
for (j = -10; j < 10000; j++) {
if (j == 0) t0 = clock();
for (i = 0; i < 10000; i++)
a[i] = KMalloc_alloc(256 * sizeof (int));
for (i = 0; i < 10000; i++)
KMalloc_free(256 * sizeof (int), a[i]);
}
t1 = clock();
printf("time: %.3f[sec]\n", (double) (t1 - t0) / CLOCKS_PER_SEC);
return 0;
}
- 最初の KAutoReleasePool_init() の呼び出しは、これをやっておけばKMallocの初期化もついでにやってくれるので、決まり文句的に書いているものです。
- これでどのくらい速くなるのかなと思ったのですが、 Core i7-2600 3.40GHz で測定したところ2.812秒でした。これは我ながらすごいです。初めは何かの間違いじゃないかと思ったほどです。12.5倍くらい高速になっています。
- (c) さらに一歩進んで、KPtrPoolを直接使ってみます。KMallocでは同じサイズを何度もalloc/freeするのであれば、KPtrPoolを直接使って高速化してくれということになっています。だからそうしてみたわけです。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "kclib1.h"
int main()
{
int i, j, **a = malloc(10000 * sizeof (int *));
clock_t t0 = 0, t1;
KAutoReleasePool_init();
KPtrPool *pool = &KMalloc_work.p[KMalloc_index(256 * sizeof (int))];
for (j = -10; j < 10000; j++) {
if (j == 0) t0 = clock();
for (i = 0; i < 10000; i++)
a[i] = KPtrPool_alloc(pool);
for (i = 0; i < 10000; i++)
KPtrPool_free(pool, a[i]);
}
t1 = clock();
printf("time: %.3f[sec]\n", (double) (t1 - t0) / CLOCKS_PER_SEC);
return 0;
}
- これを実行すると、 Core i7-2600 3.40GHz で測定したところ1.903秒でした。
- ちなみにまだ速くする余地はあって、それは「今どのサイズのメモリブロックがいくつ使われているか」の集計機能をOFFにすることです(デフォルトではデバッグ支援のためにONにしてあります)。本家のmalloc/freeにはそんな機能は無いことですし、だからそれを同等にそろえて比較するのなら、このモードで比較する意味はあるでしょう。・・・そうすると、1.882秒になります。これは実に18.7倍くらい速いことになります。
- (d) 今度はメモリ効率の確認です。まずは以下のプログラムを実行してみました。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i, *p;
char tmp[10000];
printf("[phase1] continue? (y/ctrl-c):");
scanf("%s", tmp);
for (i = 0; i < 1000000; i++) {
p = malloc(sizeof (int));
*p = i;
}
printf("[phase2] continue? (y/ctrl-c):");
scanf("%s", tmp);
return 0;
}
- これを実行すると、最初の消費メモリは356KBで、次の時点では16,312KBになっていました。つまり15,956KBほど消費メモリが増えたということです。どうやら1回のmallocで16バイトずつ消費しているようです。そもそもmallocは16バイトアラインするというルールもありましたよね、確か。
- (e) これに対して以下のプログラムだとどうでしょうか?
#include <stdio.h>
#include "kclib1.h"
int main()
{
int i, *p;
char tmp[10000];
KAutoReleasePool_init();
printf("[phase1] continue? (y/ctrl-c):");
scanf("%s", tmp);
for (i = 0; i < 1000000; i++) {
p = KMalloc_alloc(sizeof (int));
*p = i;
}
printf("[phase2] continue? (y/ctrl-c):");
scanf("%s", tmp);
return 0;
}
- これを実行すると、最初の消費メモリは420KBで、ライブラリのワークエリアの分だけ消費メモリが増えていますが、次の時点では4,656KBで済みます。だから1回のmallocで4バイトずつ消費しているのです。まあsizeof (int)が4バイトなので、当然と言えば当然の結果です。KMallocは4バイトのメモリに対しては4バイトのアラインは保証しますが、16バイトアラインは保証しないのです。