web-dev-qa-db-ja.com

ガベージコレクタなしのDプログラミング

私は今日Dを見てきました、そして表面上それはかなり驚くべきように見えます。言語に直接多くの高レベルの構造が含まれているので、ばかげたハックや簡潔なメソッドを使用する必要がないのが好きです。 GCの場合に本当に心配することが1つあります。私はこれが大きな問題であることを知っており、それについて多くの議論を読んだことがあります。

ここでの質問から生まれた私自身の簡単なテストは、GCが非常に遅いことを示しています。同じことを行うストレートC++よりも10倍以上遅い。 (明らかに、テストは実際の世界に直接変換されませんが、パフォーマンスの低下は極端であり、同様に動作する現実の世界が遅くなります(多くの小さなオブジェクトをすばやく割り当てます)

リアルタイムの低遅延オーディオアプリケーションの作成を検討していますが、GCによってアプリケーションのパフォーマンスが低下し、ほとんど役に立たなくなる可能性があります。ある意味で、問題があると、リアルタイムのオーディオの側面が台無しになります。これは、グラフィックスとは異なり、オーディオがはるかに高いフレームレート(44000+対30-60)で実行されるためです。 (レイテンシーが低いため、大量のデータをバッファリングできる標準のオーディオプレーヤーよりも重要です)

GCを無効にすると、結果がC++コードの約20%以内に改善されました。これは重要です。最後に分析用のコードを示します。

私の質問は次のとおりです。

  1. Gに依存するライブラリを引き続き使用できるように、DのGCを標準のスマートポインタ実装に置き換えることはどれほど難しいか。 GCを完全に削除すると、DにはC++と比較してすでに制限ライブラリがあるため、多くの不平を言う作業が失われます。
  2. GC.Disableはガベージコレクションを一時的に停止するだけで(GCスレッドが実行されないようにします)、GC.Enableは中断したところから再開します。そのため、レイテンシの問題を防ぐために、CPU使用率の高い瞬間にGCが実行されないようにする可能性があります。
  3. GCを一貫して使用しないようにパターンを強制する方法はありますか? (これは、私がDでプログラミングしていないためです。また、GCを使用しないメガネを書き始めるときは、独自のクリーンアップを実装することを忘れないでください。
  4. DのGCを簡単に交換することは可能ですか? (私がしたいというわけではありませんが、いつかGCのさまざまな方法を試してみるのは楽しいかもしれません...これは私が思う1に似ています)

私がやりたいのは、メモリを速度と交換することです。 GCを数秒ごとに実行する必要はありません。実際、データ構造に対して独自のメモリ管理を適切に実装できれば、それほど頻繁に実行する必要がなくなる可能性があります。メモリが不足したときにのみ実行する必要があるかもしれません。しかし、私が読んだことから、あなたがそれを呼ぶのを長く待つほど、それは遅くなります。私のアプリケーションでは通常、問題なく呼び出すことができる場合があるため、これはプレッシャーの一部を軽減するのに役立ちます(ただし、繰り返しになりますが、呼び出すことができない時間もあります)。

メモリの制約についてはそれほど心配していません。私は速度よりもメモリを「無駄にする」ことを好みます(もちろん、ある程度まで)。何よりもまず、遅延の問題です。

私が読んだことから、GCに依存するライブラリや言語構造を使用しない限り、少なくともC/C++のルートに進むことができます。問題は、私はそうするものを知らないということです。文字列、新規などについて言及しましたが、GCを有効にしないと、文字列のビルドを使用できないということですか?

GCが本当にバグがある可能性があり、パフォーマンスの問題を説明できる可能性があるというバグレポートをいくつか読んだことがありますか?

また、Dはもう少しメモリを使用します。実際、DはC++プログラムの前にメモリを使い果たします。この場合は約15%多いと思います。それはGC用だと思います。

次のコードは平均的なプログラムを表していないことはわかっていますが、プログラムが多くのオブジェクトをインスタンス化する場合(たとえば、起動時)、それらははるかに遅くなります(10倍が大きな要因です)。 GCのうち、起動時に「一時停止」される可能性がある場合は、必ずしも問題になるとは限りません。

本当に素晴らしいのは、特に割り当てを解除しない場合に、コンパイラにローカルオブジェクトを自動的にGCさせることができればです。これはほとんど両方の長所を与えます。

例えば。、

{
    Foo f = new Foo();
    ....
    dispose f; // Causes f to be disposed of immediately and treats f outside the GC
               // If left out then f is passed to the GC.
               // I suppose this might actually end up creating two kinds of Foo 
               // behind the scenes. 

    Foo g = new manualGC!Foo();   // Maybe something like this will keep GC's hands off 
                                  // g and allow it to be manually disposed of.
}

実際、さまざまなタイプのGCをさまざまなタイプのデータに関連付けて、各GCを完全に自己完結させることができれば便利かもしれません。このようにして、GCのパフォーマンスを自分のタイプに合わせて調整することができました。

コード:

module main;
import std.stdio, std.conv, core.memory;
import core.stdc.time;

class Foo{
    int x;
    this(int _x){x=_x;}
}

void main(string args[]) 
{

    clock_t start, end;
    double cpu_time_used;


    //GC.disable();
    start = clock();

    //int n = to!int(args[1]);
    int n = 10000000;
    Foo[] m = new Foo[n];

    foreach(i; 0..n)
    //for(int i = 0; i<n; i++)
    {
        m[i] = new Foo(i);
    }

    end = clock();
    cpu_time_used = (end - start);
    cpu_time_used = cpu_time_used / 1000.0;
    writeln(cpu_time_used);
    getchar();
}

C++コード

#include <cstdlib>
#include <iostream>
#include <time.h>
#include <math.h>
#include <stdio.h>

using namespace std;
class Foo{
public:
    int x;
    Foo(int _x);

};

Foo::Foo(int _x){
    x = _x;
}

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

    int n = 120000000;
    clock_t start, end;
    double cpu_time_used;




    start = clock();

    Foo** gx = new Foo*[n];
    for(int i=0;i<n;i++){
        gx[i] = new Foo(i);
    }


    end = clock();
    cpu_time_used = (end - start);
    cpu_time_used = cpu_time_used / 1000.0;
    cout << cpu_time_used;

    std::cin.get();
    return 0;
}
40
  1. Dは、必要な関数を定義するだけで、ほとんどすべてのCライブラリを使用できます。 DはC++ライブラリも使用できますが、Dは特定のC++構造を理解していません。つまり... DcanはC++とほぼ同じ数のライブラリを使用できます。それらはネイティブのDライブラリではありません。

  2. Dのライブラリリファレンスから。
    Core.memory:

    static nothrow void disable();
    

    プロセスのフットプリントを最小限に抑えるために実行される自動ガベージコレクションを無効にします。収集は、メモリ不足状態など、実装がプログラムの正しい動作に必要であると見なした場合に引き続き発生する可能性があります。この関数は再入可能ですが、無効にするには、呼び出しごとに1回有効を呼び出す必要があります。

    static pure nothrow void free(void* p);
    

    Pによって参照されるメモリの割り当てを解除します。 pがnullの場合、アクションは発生しません。 pがこのガベージコレクタによって最初に割り当てられていないメモリを参照している場合、またはメモリブロックの内部を指している場合、アクションは実行されません。 FINALIZE属性が設定されているかどうかに関係なく、ブロックはファイナライズされません。ファイナライズが必要な場合は、代わりに削除を使用してください。

    static pure nothrow void* malloc(size_t sz, uint ba = 0);
    

    ガベージコレクタから管理メモリの整列ブロックを要求します。このメモリは、freeの呼び出しで自由に削除することも、コレクションの実行中に自動的に破棄してクリーンアップすることもできます。割り当てが失敗した場合、この関数はOutOfMemoryErrorをスローすると予想されるonOutOfMemoryを呼び出します。

    あ、はい。詳細はこちら: http://dlang.org/garbage.html

    そしてここに: http://dlang.org/memory.html

    本当にクラスが必要な場合は、これを見てください: http://dlang.org/memory.html#newdelete deleteは非推奨になりましたが、それでもfree()できると思います。

  3. クラスを使用せず、構造体を使用してください。構造体はスタックに割り当てられ、クラスはヒープです。ポリモーフィズムやクラスがサポートする他のものが必要でない限り、それらはあなたがしていることのオーバーヘッドです。必要に応じて、mallocと無料を使用できます。

  4. 多かれ少なかれ...ここに関数の定義を記入してください: https://github.com/D-Programming-Language/druntime/blob/master/src/gcstub/gc.d 。 GCをカスタマイズできるように設定されたGCプロキシシステムがあります。ですから、デザイナーがあなたに望んでいないことではありません。

ここでのGCの知識はほとんどありません。ガベージコレクタは、参照されていないすべてのオブジェクトに対してデストラクタを実行することが保証されていません。さらに、ガベージコレクタが非参照オブジェクトのデストラクタを呼び出す順序は指定されていません。これは、ガベージコレクターが、ガベージコレクションされたオブジェクトへの参照であるメンバーを持つクラスのオブジェクトのデストラクタを呼び出すと、それらの参照が無効になる可能性があることを意味します。これは、デストラクタがサブオブジェクトを参照できないことを意味します。デストラクタはガベージコレクタによって実行されていないため、このルールは自動オブジェクトまたはDeleteExpressionで削除されたオブジェクトには適用されません。つまり、すべての参照が有効です。

インポートstd.c.stdlib;それはmallocと無料を持っている必要があります。

core.memoryをインポートします。これには、GC.malloc、GC.free、GC.addroots、//外部メモリをGCに追加します。

文字列は不変の文字の動的配列であるため、GCが必要です。 (immutable(char)[])動的配列にはGCが必要ですが、静的配列には必要ありません。

手動管理が必要な場合は、先に進んでください。

import std.c.stdlib;
import core.memory;

char* one = cast(char*) GC.malloc(char.sizeof * 8);.
GC.free(one);//pardon me, I'm not used to manual memory management. 
//I am *asking* you to edit this to fix it, if it needs it.

なぜintのラッパークラスを作成するのですか?あなたは物事を遅くし、メモリを浪費するだけです。

class Foo { int n; this(int _n){ n = _n; } }
writeln(Foo.sizeof);  //it's 8 bytes, btw
writeln(int.sizeof);  //Its *half* the size of Foo; 4 bytes.


Foo[] m;// = new Foo[n]; //8 sec
m.length=n; //7 sec minor optimization. at least on my machine.
foreach(i; 0..n)
    m[i] = new Foo(i);


int[] m;
m.length=n; //Nice formatting. and default initialized to 0
//Ooops! forgot this...
foreach(i; 0..n)
    m[i] = i;//.145 sec

本当に必要な場合は、Cで時間依存関数を記述し、Dから呼び出します。Heck、時間が本当に重要な場合は、Dのインラインアセンブリを使用してすべてを最適化します

19
0b1100110

この記事を読むことをお勧めします: http://3d.benjamin-thaut.de/?p=2 そこには、独自のメモリ管理を行い、ガベージコレクションを完全に回避する標準ライブラリのバージョンがあります。 。

9
DejanLekic

DのGCは、Javaのような他のGCほど洗練されていません。オープンソースなので、誰でも改善を試みることができます。

CDGCという名前の実験的な同時GCがあり、グローバルロックを削除する現在のGSoCプロジェクトがあります: http://www.google-melange.com/gsoc/project/google/gsoc2012/avtuunainen/17001 ==

より最適化されたコードを取得するには、コンパイルにLDCまたはGDCを使用してください。

XomBプロジェクトもカスタムランタイムを使用していますが、Dバージョン1だと思います。 http://wiki.xomb.org/index.php?title=Main_Page

5
Trass3r

必要なすべてのメモリブロックを割り当ててから、メモリプールを使用してGCなしでブロックを取得することもできます。

ちなみに、あなたが言ったほど遅くはありません。また、GC.disable()は実際には無効にしません。

4
phaazon

問題を少し異なる視点から見るかもしれません。質問の根拠として言及している、多くの小さなオブジェクトを割り当てるという次善のパフォーマンスは、GCだけとはほとんど関係がありません。むしろ、それは汎用(しかし最適ではない)と高性能(しかしタスクに特化した)メモリ管理ツールの間のバランスの問題です。アイデアは次のとおりです。GCが存在しても、リアルタイムアプリの作成が妨げられることはありません。特別な場合には、より具体的なツール(たとえば、 オブジェクトプール )を使用する必要があります。

3
vines

これはまだ閉じられていないため、最近のバージョンのDには、組み込み配列よりもメモリに関して大幅に効率的な配列データ構造を含むstd.containerライブラリがあります。ライブラリ内の他のデータ構造も効率的であるかどうかは確認できませんが、ガベージコレクションを必要としないデータ構造を手動で作成することなく、メモリをより意識する必要があるかどうかを調べる価値があるかもしれません。

2
blockcipher