mallocの効率と実装の違いは?


8

私が使用している場合はmalloc、ないmallocにかかわらず、常にそれが配分されているものと同じアルゴリズムを使用するか、それがデータを見て、appriopriateアルゴリズムを選択しますか?

より効率的なアルゴリズムを選択することにより、mallocをより速くまたはよりスマートにすることができますか?私のテストではmalloc、テスト結果が正しければ、Ubuntuの組み込み公式システムは学校プロジェクトよりも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;
}

7
可変サイズのチャンクを割り当てたり割り当て解除したりしているときと、割り当てと同じ順序で解放が呼び出されていないときのパフォーマンスを比較して、学校のプロジェクトで何が起こるかを確認してください。
whatsisname '20年

1
Cライブラリのソースはおそらくgnu.org/software/libcにあります -または、パッケージマネージャーを使用してソースをダウンロードできます。
kdgregory

1
アロケータが標準ライブラリよりも高速または低速である理由に関するコメントが必要な場合は、テストプログラムだけでなく、それを表示する必要があります。大量のメモリブロックを事前に割り当ててからチャンクを分割すると思います。つまり、ヒープサイズを段階的に増やしていくコストsbrk(または最新のアロケータが使用するもの)を支払う必要はありません。
kdgregory 2016年

1
そして無関係、なぜcallocそしてそれから明確に?
kdgregory 2016年

@whatsisname私はあなたのコメントに従ってテストを変更しました、そして私の習慣mallocが遅いよりも私は合理的な結果を得ました。それが私が期待することです。
Niklas

回答:


11

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

念頭に置いてベアmalloc実装は、上に行くには非常に少ない情報、要求された割り当てのサイズだけを持っています。free実装はポインタのみを持ち、どのようなデータ「malloc関数は、」密かにそれに接続されている可能性があります。そのため、割り当てや割り当て解除の方法を決定する際に、かなりの量のヒューリスティック(つまり、推測に基づく推測)が発生します。

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

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

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


良い記事へのリンクの+1。理論を勉強する必要があります。valloc今まで聞いたことがないことを偶然見つけました。それが何であるかを確認する必要があります。
Niklas

2
スレッドセーフを忘れないでください。
Sebastian Redl

vallocメモリをページサイズに揃えて返します。使用できるので非推奨ですmemalign
アレックス

19

効率のみを重視する場合は、標準に準拠した非常に効率的な実装を次に示します

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

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

私はあなたがより速く見つけることはないと確信していますmalloc

しかし、標準に準拠している間は、その実装は役に立ちません(ヒープメモリを正常に割り当てることはできません)。それは冗談実装です。

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

Linuxシステムでは、少なくとも(ほぼ)すべてのC標準ライブラリフリーソフトウェアであるため、それらのソースコード(特に&のコード)を調べることができます。musl-libcには、非常に読みやすいソースコードがあります。mallocfree

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

高度なLinuxプログラミングやオペレーティングシステムに関するより一般的な資料(オペレーティングシステム:3つの簡単な部分など)を読むとよいでしょう。

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

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

注意:Linuxシステムでmallocの失敗をテストするには(Linuxでmmap(2)および/またはsbrk(2)システムコールを呼び出して仮想アドレス空間を拡張mallocする場合がありますが、ほとんどの場合、以前のdメモリを再利用しようとします) 、あなたは呼ぶかもしれないはsetrlimit(2)を適切にして、および/または、しばしばスルーターミナルシェルのbashの組み込み関数。strace(1)を使用して、ユーザーの(または他の)プログラムによって実行されたシステムコールを見つけます。freeRLIMIT_ASRLIMIT_DATAulimit


信頼性は気になりますが、効率/速度を理解する方が簡単です。割り込みなどが発生するとmallocがクラッシュする可能性があることを読みましたが、それについてはまだよくわかりません。テストコードが間違っていると指摘してくれてありがとう、私は結果が無理だと思った。コードをランダム割り当てに変更しました。結論はもっと勉強すべきだと思います。
Niklas

mallocが失敗しない実装もあります(ただし、プログラムがクラッシュする可能性があります)。iOSでmallocがS nullポインターを返すかどうかのテストはまったく無意味です。
gnasher729 2016年

私は(例えば、いくつかのLinuxコンピュータがメモリオーバーコミットを持っている)ことを知っているが、そのような実装であることを私は予告を行うことに対して、 Cの標準:そのmalloc(非帰国あるNULL逆参照することができませんでした)ポインタを。
バジルStarynkevitch

6

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


5

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

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

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

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

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

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

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


2

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


2

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.