web-dev-qa-db-ja.com

mallocはどの程度効率的で、実装はどのように異なりますか?

mallocを使用する場合、mallocは、何を割り当てているかに関係なく、常に同じアルゴリズムを使用しますか、それともデータを調べて適切なアルゴリズムを選択しますか?

より効率的なアルゴリズムを選択することにより、mallocをより速くまたはよりスマートにすることができますか?私のテストでは、テスト結果が正しければ、Ubuntuの組み込み公式システムmallocは学校のプロジェクトよりも10倍遅くなります。キャッチとは何ですか?最適化が必要なため、テストでmallocのパフォーマンスが非常に悪いことに驚いています。常に同じアルゴリズムを使用していますか? mallocのリファレンス実装はありますか? mallocのソースを確認したい場合、どこを見ればよいですか?私が実行するテストは次のとおりです。

/* returns an array of arrays of char*, all of which NULL */
char ***alloc_matrix(unsigned rows, unsigned columns) {
    char ***matrix = malloc(rows * sizeof(char **));
    unsigned row = 0;
    unsigned column = 0;
    if (!matrix) abort();

    for (row = 0; row < rows; row++) {
        matrix[row] = calloc(columns, sizeof(char *));
        if (!matrix[row]) abort();
        for (column = 0; column < columns; column++) {
            matrix[row][column] = NULL;
        }
    }
    return matrix;
}

/* deallocates an array of arrays of char*, calling free() on each */
void free_matrix(char ***matrix, unsigned rows, unsigned columns) {
    unsigned row = 0;
    unsigned column = 0;
    for (row = 0; row < rows; row++) {
        for (column = 0; column < columns; column++) {
            /*    printf("column %d row %d\n", column, row);*/
            free(matrix[row][column]);
        }
        free(matrix[row]);
    }
    free(matrix);
}


int main(int agrc, char **argv) {

    int x = 10000;
    char *** matrix = alloc_matrix(x, x);
    free_matrix(matrix, x, x);
    return (0);
}

テストは大丈夫ですか?私もこのテストを使用します:

   for (i = 0; i < 1000000; i++) {
        void *p = malloc(1024 * 1024 * 1024);
        free(p);
   }
  • 更新

コメントによると、可変サイズのチャンクを作成し、割り当てとは異なる順序で解放する必要があるので、次のことを試みます。

int main(int agrc, char **argv) {
     int i;
    srand(time(NULL));
    int randomnumber;
    int size = 1024;
    void *p[size];
    for (i = 0; i < size; i++) {
        randomnumber = Rand() % 10;
        p[i] = malloc(1024 * 1024 * randomnumber);
    }

    for (i = size-1; i >= 0; i--) {
        free(p[i]);
    }

    int x = 1024;
    char *** matrix = alloc_matrix(x, x);
    free_matrix(matrix, x, x);
    return (0);
}

それから私のカスタムmallocはもはや速くありません:

$ time ./gb_quickfit 

real    0m0.154s
user    0m0.008s
sys 0m0.144s
dac@dac-Latitude-E7450:~/ClionProjects/omalloc/openmalloc/overhead$ time ./a.out 

real    0m0.014s
user    0m0.008s
sys 0m0.004s

私が使用したアルゴリズムは:

void *malloc_quick(size_t nbytes) {

    Header *moreroce(unsigned);
    int index, i;
    index = qindex(nbytes);

    /* 
     * Use another strategy for too large allocations. We want the allocation
     * to be quick, so use malloc_first().
     */

    if (index >= NRQUICKLISTS) {
        return malloc_first(nbytes);
    }

    /* Initialize the quick fit lists if this is the first run. */
    if (first_run) {
        for (i = 0; i < NRQUICKLISTS; ++i) {
            quick_fit_lists[i] = NULL;
        }
        first_run = false;
    }


    /*
     * If the quick fit list pointer is NULL, then there are no free memory
     * blocks present, so we will have to create some before continuing.
     */

    if (quick_fit_lists[index] == NULL) {
        Header* new_quick_fit_list = init_quick_fit_list(index);
        if (new_quick_fit_list == NULL) {
            return NULL;
        } else {
            quick_fit_lists[index] = new_quick_fit_list;
        }
    }

    /*
     * Now that we know there is at least one free quick fit memory block,
     * let's use return that and also update the quick fit list pointer so that
     * it points to the next in the list.
     */

    void* pointer_to_return = (void *)(quick_fit_lists[index] + 1);
    quick_fit_lists[index] = quick_fit_lists[index]->s.ptr;
   /* printf("Time taken %d seconds %d milliseconds", msec/1000, msec%1000);*/
    return pointer_to_return;
}
8
Niklas

mallocには複数の実装があり、非常に異なるアルゴリズムを使用できます。 2つの非常に広く使われている実装は jemallocdlmalloc です。どちらの場合も、リンクには、汎用アロケータで行われた思考プロセスとトレードオフに関する多くの情報があります。

mallocの実装には、要求される割り当てのサイズだけで、次に進むべき情報はほとんどありません。 freeの実装にはポインタしかなく、 'malloc'が密かにそれに接続している可能性のあるデータはすべてあります。そのため、割り当てと割り当て解除の方法を決定する際に、かなりの量のヒューリスティック(つまり、推測に基づく推測)が発生します。

アロケータが対処する必要があるいくつかの問題は次のとおりです。

  1. アラインメント-一部のメモリアラインメントは他よりも高速です
  2. 断片化-単純な割り当てと解放は、肥大化を引き起こす穴を残す可能性があります
  3. パフォーマンス-メモリを増やすためにOSに戻るとコストがかかる可能性があります
  4. 一般的なリクエスト-実際のアプリケーション(esp C++)では、多くの小さな割り当てが行われることが多いため、これらを効率的にすると、

これらすべてを考慮に入れると、アロケータはソフトウェアの複雑な部分である傾向があるため、一般的な使用法では、アロケータは適切に機能します。ただし、特定のケースでは、データの構造について多くのことをよく知っている場合は、アロケータの外部でメモリを管理する方がよい場合があります。明らかに欠点は、自分で作業を行う必要があることです。

11
Alex

効率についてのみを気にする場合は、標準に準拠した非常に効率的な実装を次に示します。

void* malloc(size_t sz) {
  errno = ENOMEM;
  return NULL;
}

void free(void*p) {
  if (p != NULL) abort();
}

mallocが速くなることはないと思います。

ただし、標準に準拠している間は、その実装は役に立たないです(ヒープメモリの割り当てに成功することはありません)。それは冗談実装です。

これは、現実の世界では、mallocfreeの実装でトレードオフを行う必要があることを示しています。そして、さまざまな実装がさまざまなトレードオフを行っています。 malloc implementations に関する多くのチュートリアルがあります。 Cプログラムで メモリリーク をデバッグするには、 valgrind を使用します。

Linuxシステムでは、少なくとも(ほぼ)すべての C標準ライブラリフリーソフトウェア なので、研究できることに注意してください。ソースコード(特にmallocfreeのコード)。 musl-libc には、非常に読みやすいソースコードがあります。

ところで、あなたの質問のコードは間違っています(そして上記のmallocでクラッシュします)。 malloccanへのすべての呼び出しは失敗するので、テストする必要があります。

Advanced Linux Programming や、オペレーティングシステムに関するいくつかのより一般的な資料(例: オペレーティングシステム:3つの簡単な部分

少なくとも主な概念と用語を理解するには、おそらく ガベージコレクション について何かを読む必要があります。おそらく GCハンドブック を読んでください。 参照カウント は、GC(貧弱なもので、うまく処理できない参照サイクルまたは 循環グラフ)の形式と見なすことができます ...)。

Cプログラムで使用することもできます Boehmの保守的なガベージコレクター :次に、mallocの代わりにGC_MALLOC(または、文字列や数値配列などのポインターのないデータの場合はGC_MALLOC_ATOMIC)を使用します。 freeを呼び出す必要はもうありません。 BoehmのGCを使用する場合、いくつかの注意点があります。他のGCスキームまたはライブラリを検討するかもしれません...

注意:Linuxシステムでmallocの失敗をテストするには(mallocmmap(2)sbrk(2) システムコールを呼び出すことがありますLinuxは virtual address space を拡張しますが、ほとんどの場合、以前はfreedメモリを再利用しようとしますが、RLIMIT_ASsetrlimit(2) を適切に呼び出すことができます。および/またはRLIMIT_DATA。多くの場合、ターミナルシェルのulimit bashビルトインを使用します。 strace(1) を使用して、(または他の)プログラムによって実行されたシステムコールを見つけます。

19

まず、mallocfreeは一緒に機能するため、mallocを単独でテストすると誤解を招きます。第二に、それらがどれほど優れていても、それらはどのアプリケーションでも簡単に主要なコストになる可能性があります。そのための最善の解決策は、呼び出しを少なくすることです。それらを少なく呼び出すことは、ほとんどの場合malloc-limitedであるプログラムを修正するための最良の方法です。これを行う一般的な方法の1つは、使用済みオブジェクトをリサイクルすることです。 free-ingではなく、ブロックを使い終わったら、それを使用済みブロックスタックにチェーンし、次に必要になったときに再利用します。

6
Mike Dunlavey

malloc_quick()実装の主な問題は、スレッドセーフではないことです。そして、はい、アロケータからスレッドサポートを省略した場合、パフォーマンスを大幅に向上させることができます。独自の非スレッドセーフアロケーターで同様の高速化を見たことがあります。

ただし、標準実装はスレッドセーフである必要があります。次のすべてを考慮する必要があります。

  • 異なるスレッドがmalloc()free()を同時に使用します。つまり、実装は内部同期なしではグローバル状態にアクセスできません。

    ロックは非常に負荷が高いため、一般的なmalloc()の実装では、ほとんどすべての要求にスレッドローカルストレージを使用することにより、グローバル状態をできるだけ使用しないようにしています。

  • ポインターを割り当てるスレッドは、必ずしもそれを解放するスレッドであるとは限りません。これには注意が必要です。

  • スレッドは常にメモリを割り当て、それを解放するために別のスレッドに渡します。これは、空きブロックがスレッドローカルストレージ内に蓄積する可能性があることを意味するため、最後のポイントの処理がはるかに困難になります。これにより、malloc()実装がスレッドに空きブロックを交換する手段を提供するように強制され、おそらくロックを時々取得する必要があります。

スレッドを気にしない場合、これらの点はすべて問題ではないので、非スレッドセーフアロケーターは、パフォーマンスでの処理にお金を払う必要はありません。しかし、私が言ったように、標準の実装はこれらの問題を無視することができず、その結果、それらの処理にお金を払わなければなりません。

2つのSUTは直接の比較ではないと思います。メモリの製造、マザーボードのアーキテクチャ、コンパイラのバージョン(mallocをコンパイルしたもの)、当時のSUTでのメモリ空間のアプリケーションの状態など、すべての変数を考慮しても、同等の違いに驚くことはありません。 ....実際のプロジェクトでメモリをどのように使用するかをより具体的に表すために、テストハーネスを使用してみてください。大小の割り当てと、長期間保持され、一部のアプリケーションは取得後すぐに解放されるものがあります。

2
Wayne Booth

実際のmallocの実装を学校のプロジェクトと比較する場合は、実際のmallocが割り当て、再割り当て、および非常に異なるサイズのメモリの解放を管理し、割り当てが異なるスレッドで同時に発生し、メモリの再割り当てと解放が異なるスレッドで発生する場合に正しく機能する必要がある。また、mallocで割り当てられなかったメモリを解放しようとする試みが確実にキャッチされるようにする必要もあります。そして最後に、デバッグ用のmalloc実装と比較しないようにしてください。

2
gnasher729