3

この質問は、ソースコードが原因で少し長くなります。ソースコードはできるだけ単純化しようとしました。ご容赦ください。一緒に読んでくれてありがとう。

何百万回も実行される可能性のあるループを持つアプリケーションがあります。mallocそのループ内で数千から数百万の/呼び出しの代わりに、最初に1 つ、次に数千から数百万の呼び出しfreeを実行したいと考えています。mallocrealloc

しかし、私が使用しているときに、アプリケーションが数 GB のメモリを消費し、それ自体を強制終了するという問題が発生していますrealloc。を使用するmallocと、メモリ使用量は問題ありません。

の memtestを使用して小規模なテスト データ セットを実行すると、またはvalgrindのいずれでもメモリ リークは報告されません。mallocrealloc

すべてのmalloc-ed (およびrealloc-ed) オブジェクトと対応するfree.

したがって、理論的には、メモリリークはしていませんrealloc。使用可能なRAMをすべて消費しているように見えるだけです。その理由とこれを修正するためにできることを知りたいです。

私が最初に持っているのは、次のようなもので、malloc適切に使用および機能します。

マロックコード

void A () {
    do {
        B();
    } while (someConditionThatIsTrueForMillionInstances);
}

void B () {
    char *firstString = NULL;
    char *secondString = NULL;
    char *someOtherString;

    /* populate someOtherString with data from stream, for example */

    C((const char *)someOtherString, &firstString, &secondString);

    fprintf(stderr, "first: [%s] | second: [%s]\n", firstString, secondString);

    if (firstString)
        free(firstString);
    if (secondString)
        free(secondString);
}

void C (const char *someOtherString, char **firstString, char **secondString) {
    char firstBuffer[BUFLENGTH];
    char secondBuffer[BUFLENGTH];

    /* populate buffers with some data from tokenizing someOtherString in a special way */

    *firstString = malloc(strlen(firstBuffer)+1);
    strncpy(*firstString, firstBuffer, strlen(firstBuffer)+1);

    *secondString = malloc(strlen(secondBuffer)+1);
    strncpy(*secondString, secondBuffer, strlen(secondBuffer)+1);
}

これはうまくいきます。しかし、私はもっと速いものが欲しいです。

reallocここで、配置をテストします。これはmalloc-s が 1 回だけです。

再割り当てコード

void A () {
    char *firstString = NULL;
    char *secondString = NULL;

    do {
        B(&firstString, &secondString);
    } while (someConditionThatIsTrueForMillionInstances);

    if (firstString)
        free(firstString);
    if (secondString)
        free(secondString);
}

void B (char **firstString, char **secondString) {
    char *someOtherString;

    /* populate someOtherString with data from stream, for example */

    C((const char *)someOtherString, &(*firstString), &(*secondString));

    fprintf(stderr, "first: [%s] | second: [%s]\n", *firstString, *secondString);
}

void C (const char *someOtherString, char **firstString, char **secondString) {
    char firstBuffer[BUFLENGTH];
    char secondBuffer[BUFLENGTH];

    /* populate buffers with some data from tokenizing someOtherString in a special way */

    /* realloc should act as malloc on first pass through */

    *firstString = realloc(*firstString, strlen(firstBuffer)+1);
    strncpy(*firstString, firstBuffer, strlen(firstBuffer)+1);

    *secondString = realloc(*secondString, strlen(secondBuffer)+1);
    strncpy(*secondString, secondBuffer, strlen(secondBuffer)+1);
}

free -m100 万ループ状態を引き起こす大規模なデータ セットを使用してこの ベースのテストを実行しているときに、コマンド ラインで の出力を見るとrealloc、メモリが 4 GB から 0 に減少し、アプリがクラッシュします。

reallocこれを引き起こしている使用について何が欠けていますか? ばかげた質問で申し訳ありませんが、アドバイスをよろしくお願いします。

4

3 に答える 3

8

reallocサイズ変更操作を適切に実行できない場合は、内容を古いバッファーから新しいバッファーにコピーする必要があります。元のメモリを保持する必要がない場合は、 malloc/freeペアの方が優れている可能性があります。realloc

そのため、一時的に/ペアreallocよりも多くのメモリが必要になる可能性があります。また、を継続的にインターリーブすることにより、断片化を促進しています。つまり、基本的に次のことを行っています。mallocfreerealloc

malloc(A);
malloc(B);

while (...)
{
    malloc(A_temp);
    free(A);
    A= A_temp;
    malloc(B_temp);
    free(B);
    B= B_temp;
}

元のコードは次のようになります。

while (...)
{
    malloc(A);
    malloc(B);
    free(A);
    free(B);
}

2番目の各ループの終わりに、使用したすべてのメモリをクリーンアップしました。これは、すべてを完全に解放せずにメモリ割り当てをインターリーブするよりも、グローバルメモリヒープをクリーンな状態に戻す可能性が高くなります。

于 2010-11-15T23:57:15.170 に答える
1

reallocメモリ ブロックの既存の内容を保存したくない場合に を使用するのは、非常に悪い考えです。何もしなければ、上書きしようとしているデータの複製に多くの時間を浪費することになります。実際には、あなたが使用している方法では、サイズ変更されたブロックは古いスペースに収まらないため、ヒープ上のアドレスが徐々に高くなり、ヒープが途方もなく大きくなります。

メモリ管理は簡単ではありません。不適切な割り当て戦略は、断片化、ひどいパフォーマンスなどにつながります。できる最善の方法は、絶対に必要な以上の制約を導入することを避け (realloc必要のないときに使用するなど)、使い終わったらできるだけ多くのメモリを解放することです。 、関連付けられたデータの大きなブロックを、小さな断片ではなく単一の割り当てでまとめて割り当てます。

于 2010-11-16T03:45:09.273 に答える
0

&(*firstString)と同じであることが期待されていますfirstStringが、実際には、のポインタのアドレスを渡すのではなく、関数の引数のアドレスを取得していますA。したがって、呼び出すたびに、NULLのコピーを作成し、新しいメモリを再割り当てし、新しいメモリへのポインタを失い、繰り返します。Aこれは、元のポインタの最後にまだnullがあることを確認することで簡単に確認できます。

編集:まあ、それは素晴らしい理論ですが、私がテストするために利用できるコンパイラについては間違っているようです。

于 2010-11-15T23:58:49.973 に答える