Dのオフィスとクリーニング

こんにちは、Habr!



Dは、ガベージコレクターを使用することを知っています。 彼はメモリの割り当てを制御します。 連想配列や動的配列、文字列(配列でもあります)、例外、デリゲートなどの組み込み型の実装はそれを使用します。 また、その使用は言語の構文(連結、new演算子)に埋め込まれています。 GCはプログラマーの責任と負担を取り除き、よりコンパクトで理解しやすく安全なコードを書くことができます。 そして、これらはおそらくガベージコレクターの最も重要な利点です。 拒否する価値はありますか? コレクターの使用に対する支払いは過度のメモリ消費になります。これは、非常に限られたリソースと、アセンブリ自体のすべてのスレッドの停止(ストップザワールド)で受け入れられません。 これらの点があなたにとって重要な場合、猫の下で歓迎します。





どれくらい悪いの?



あなたが最初に知る必要があることですが、それはまったく悪いですか?

ちなみに
ビルトインツールの罪は、使用されているアーキテクチャとアルゴリズムをチェックした後でなければなりません。



valgrindを使用できます。そのmemcheckツール(デフォルト)は、プログラムがメモリを割り当てて解放した回数とその量(ヒープ使用量の合計)を表示します。

ただし、valgrindはGCの使用統計を表示できません。 幸いなことに、これはランタイムDに組み込まれています(dmdのみ)。 コンパイル済みのプログラムのガベージコレクタは、次のように構成およびプロファイルできます。

app "--DRT-gcopt=profile:1 minPoolSize:16" program args
      
      





最初の引数(文字列)はランタイムによって処理され、メイン関数に到達しません。

サポートされているオプション:



プロファイリングが有効な場合、完了後のプログラム出力は次のようになります。

  Number of collections: 101 Total GC prep time: 10 milliseconds Total mark time: 3 milliseconds Total sweep time: 3 milliseconds Total page recovery time: 0 milliseconds Max Pause Time: 0 milliseconds Grand total GC time: 17 milliseconds GC summary: 67 MB, 101 GC 17 ms, Pauses 13 ms < 0 ms
      
      







組み立てなしの寿命(ほぼ)



すべてのテストとGCの調整後、結果が満足のいくものでない場合は、いくつかのトリックに頼ることができます。



コレクターは必要ありません-使用しないでください



ほんと? そうですか?

プログラムの重要なセクションでは、コレクターを単純に無効にできます。

 import core.memory; ... GC.disable(); ...
      
      





そして、「クリーニングの時間がある」場合、それをオンに戻すか、すぐに開始します。

 ... GC.enable(); GC.collect(); // enable  collect   ,    ...
      
      





完了時に、プログラムは、インクルードの状態に関係なく、ガベージコレクタを再び起動します。

この手法を使用する場合、メモリが十分ではない場合、メモリが割り当てられ続けることを覚えておくことが重要です。プログラムはOSによって終了されます。



正しいタイプを使用してください



この記事の冒頭で既に述べたように、配列、クラス、デリゲートは、GCから逃れようとするときに使用するのに最適な候補ではありません。

一部のクラスは構造に置き換えることができます。 Dでは、構造体はスタックに割り当てられ、スコープを出るときに破棄されます。 クラスのないクラスがない場合は、スコープ内でのみ使用できます。

 import std.typecons; ... auto cls = scoped!MyClass( param, of, my, _class ); ...
      
      





clsオブジェクトはMyClassクラスのインスタンスとして動作しますが、GCの関与なしにスコープを離れると破棄されます。 クラスオブジェクトを作成するためのscopeキーワードは、ライブラリの実装を優先して使用されることを置き換える価値があります。 ここで説明します。



範囲!



別のラウンドと、私が理解するように、標準ライブラリの現在の開発動向は、範囲の範囲への移行です。 したがって、std.algorithmのほとんどすべての関数が機能します。 入力、出力、無限、長さなど、範囲は異なる場合があります。

それらの意味は、これらがfront、popFrontなどの特定のメソッドを含むオブジェクト(構造)であることです。 標準ライブラリで、どの構造が範囲として機能するかについて詳しく学んでください。 それらの利点は、計算の遅延とメモリの割り当てがないことです。 簡単な例:

 import std.stdio; import std.typetuple; import std.range; import std.array; template isIRWL(R) { enum isIRWL = isInputRange!R && hasLength!R; } template FloatHandler(R) { enum FloatHandler = is( ElementType!R == float ); } float avg(R1,R2)( R1 a, R2 b ) if( allSatisfy!(isIRWL,R1,R2) && allSatisfy!(FloatHandler,R1,R2) ) { auto c = chain( a, b ); //     float res = 0.0f; foreach( val; c ) res += val; // foreach ) return res / c.length; //   InputRange   } void main() { float[] a = [1,2,3]; float[] b = [4,5,6,7]; writeln( avg( a, b ) ); // 4 float[] d = chain( a, b ).array; //    writeln( d ); // [1,2,3,4,5,6,7] }
      
      





チェーン関数は、入力で指定された範囲への2つの参照を含むResult型(関数に対してローカル)のオブジェクトを返します。 このオブジェクトをforeachで列挙すると、frontおよびpopFrontメソッドが呼び出され、このオブジェクトは最初の範囲で最初に対応するメソッドを呼び出し、次に最初の範囲が空になると2番目に対応するメソッドを呼び出します。

バンドのテーマに関する優れたプレゼンテーションは、ジョナサンMデイビスによるDConf2015で行われました。



本当にクラスが必要な場合



はい、常に作成および削除されているもの。 この場合、クラスを少し再定義して、FreeListの概念を使用できます。

 class Foo { static Foo freelist; //   Foo next; //     static Foo allocate() { Foo f; if( freelist ) //       { f = freelist; //   freelist = f.next; } else f = new Foo(); //    return f; } static void deallocate(Foo f) //       { f.next = freelist; freelist = f; } ...     ... } ... Foo f = Foo.allocate(); ... Foo.deallocate(f);
      
      





この場合、新しいオブジェクトが既に作成されており、不要になった場合、新しいオブジェクトへのメモリの割り当てを最小限に抑えます。 これにより、コレクターから完全にブロックされるわけではありませんが、メモリを割り当てないと、コレクターはアセンブリを開始しません。

ちなみに
もちろん、可能であれば、必要なすべてのメモリを事前に割り当てることをお勧めします。





組み立てなしの生活(まあ、少しだけ)



私はコンパイラを使用せずにDで完全に記述する方法を見つけませんでしたが、これは部分的には私の意見では良いことです。 手動のメモリ管理には、エラー、危険、扱いにくいなどが伴います(古くて邪悪なC ++)。 しかし、本当に必要な場合は、できます。



libc mallocおよびfreeの関数は、手動のメモリ管理に使用されます。 配列を操作するには、これが基本です。

 import core.stdc.stdlib; ... auto arr = (cast(float*)malloc(float.sizeof*count))[0..count]; ... free( arr.ptr ); ...
      
      







@nogc属性を使用して、GCの不要な使用から身を守ることができます。 コンパイラーは、この属性を持つブロック内でコレクターの使用を検出するとエラーをスローします。

 void foo() {} void func(int[] arr) @nogc { auto a = new MyClass; //  arr ~= 42; //  foo(); // :  ,    @nogc }
      
      





使用の柔軟性を維持するために、テンプレート関数に属性を指定しないください 。 テンプレート関数が@nogcコードから呼び出される場合、コンパイラは@nogcも作成しようとします。 これを行うには、このテンプレート関数内で@nogc関数のみが使用されるという条件を維持する必要があります。 このコンパイラの動作は、コレクターを使用するときにテンプレート関数が必要な場合にテンプレートコードを繰り返し使用する場合に便利です(通常のコードから呼び出され、内部で通常のコードを使用します)。 これは、他の属性(nothrow、pureなど)にも適用されます。



コンパイル時に、コレクターが使用されているプログラム内のすべての場所を表示できます。
 dmd -vgc source.d ...
      
      





コンパイラは使用場所のみを示しますが、エラーは生成しません。



標準ライブラリを使用してストリームを作成する場合、コレクタも使用されることに注意してください。 コレクタなしでスレッドを作成するには、mallocおよびfreeの場合のように、C関数を使用する必要があります。



そして最後に:コレクターなしでクラスを作成する



コメント付きの小さな例



 import std.stdio; import core.exception; import core.stdc.stdlib : malloc, free; import core.stdc.string : memcpy; import core.memory : GC; import std.traits; class A { int x; this( int X ) { x = X; } int foo() { return 2 * x; } } class B : A { int z = 2; this( int x ) { super(x); } override int foo() { return 3 * x * z; } } // std.conv.emplace    @nogc,   T classEmplace(T,Args...)( void[] chunk, auto ref Args args ) if( is(T == class) ) { enum size = __traits(classInstanceSize, T); //     //  ,    if( chunk.length < size ) return null; if( chunk.length % classInstanceAlignment!T != 0 ) return null; //  TypeInfo       init,     //          ,   memcpy( chunk.ptr, typeid(T).init.ptr, size ); auto res = cast(T)chunk.ptr; //   static if( is(typeof(res.__ctor(args))) ) res.__ctor(args); else static assert(args.length == 0 && !is(typeof(&T.__ctor)), "Don't know how to initialize an object of type " ~ T.stringof ~ " with arguments " ~ Args.stringof); return res; } auto heapAlloc(T,Args...)( Args args ) { enum size = __traits(classInstanceSize, T); auto mem = malloc(size)[0..size]; if( !mem ) onOutOfMemoryError(); //GC.addRange( mem.ptr, size ); //    return classEmplace!(T)( mem, args ); } auto heapFree(T)( T obj ) { destroy(obj); //GC.removeRange( cast(void*)obj ); //     free( cast(void*)obj ); } void main() { auto test = heapAlloc!B( 12 ); writeln( "test.foo(): ", test.foo() ); // 72 heapFree(test); }
      
      





コメントアウトされた行については、GC.addRange()およびGC.removeRange()。 コレクターを使用しないと固く決心している場合は、コメント化しておくことができます。 配列、デリゲート、他のクラスなどをGCを使用して削除する必要があるクラス内に格納する場合は、GCにメモリの範囲を追加して、ガベージを見つけるためにスキャンする必要があります。



コンストラクターが@nogcの場合、heapAllocでheapAllocを使用できます。heapFreeを使用すると、すべてがより複雑になります。破壊、デストラクタの呼び出し(mixinで簡単に実装できます)に加えて、クラスモニターに関連するアクションも実行します(もちろん、必要に応じて、 @nogcオプションに置き換えてください)。



おわりに





言語と標準ライブラリの開発では、ガベージコレクタの「強制的な」使用を放棄する傾向が見られます。 現時点では、この作業は完全にはほど遠いですが、進歩はあります。



この点で、同じDConf2015のWalter BrightAndrei Alexandrescuの報告は私にとって興味深いものでした。



PS。 どうしてhabrにはまだDを強調する構文がないのですか?

PPS ロシア連邦でD会議が計画されているかどうかは誰にもわかりませんか?



All Articles