2

断片化を防ぐ単純なスレッドセーフなメモリ管理を実装する必要があります。thisthisなどの記事を読んだことがありますが、C# で実装を開始する方法がわかりません。

重要なポイント: メモリ割り当てのリクエストの 95% は 1K 未満のブロックになります!

誰かが最初にコードを教えてくれませんか?

編集Allocator済みですが、方法でプールを使用しなかった方法を 書きましたAlloc。プールを使用するように変更するにはどうすればよいですか?

class Allocator
{
       private int _id;

       //TODO: it must be struct!
       class Block
       {
              public int offset;
              public int blockLength;
       }

       private readonly Dictionary<int, Block> idToBlock = new Dictionary<int, Block>();

       private List<byte> rawData;
       private int allocatedBlocksLength;

       // sync
       private readonly object _sync = new object();

       public int Alloc(int count)
       {                  
              rawData.AddRange(new byte[count]);
              idToBlock.Add(_id, new Block { offset = allocatedBlocksLength, blockLength = count });

              var blockId = _id;
              ++_id;
              allocatedBlocksLength += count;

              return blockId;
       }

       public void Free(int id)
       {
              // Search in table
              Block block;
              if (!idToBlock.TryGetValue(id, out block))
                     return;

              // remove object and update all offsets that after our block
              foreach (var kv in idToBlock)
              {
                     if (kv.Key == id)
                           continue;
                     if (kv.Value.offset > block.offset)
                           continue;

                     // changing indexes
                     kv.Value.offset -= block.blockLength;
              }

              // update how much left
              allocatedBlocksLength -= block.blockLength;
       }
}
4

1 に答える 1

1

.NET アプリケーションにカスタムメモリ マネージャーが必要な場合は、管理されていない世界 (2 番目のリンク) のヒントに従う (または単にコードを変換する)べきではありません。

.NET 環境でのメモリ割り当てはまったく異なります。メモリははるかに断片化されます (デフォルトのアロケータが割り当て速度を優先するため) が、圧縮することができます (したがって、メモリの断片化の問題は実際には問題になりません)。

あなたのケースではありませんが、大きなオブジェクト (最近、このしきい値は 85 KB に設定されています) は別の戦略で割り当てられ、圧縮されません。短期間の大きなオブジェクトをたくさん作成する場合にのみ、カスタムアロケーター必要になると思います。

最初のリンクは非常に素朴な実装を提供します。マルチスレッド環境でプロファイリングしましたか? あなたの場合、デフォルトの割り当てよりも優れたパフォーマンスを発揮しますか? パフォーマンスが少し良くなったとしても、本当に必要ですか?

メモリ アロケータをスレッド セーフにするには、スレッドごとに異なるヒープを使用するか、単にデータ構造をロックします (たとえば、空きメモリ ブロックのリストを LinkedList 内に保持する場合、リストからノードを削除するときに構造をロックできます)。 )。これは、数行で説明できるトピックではありません。これらの内部構造に本当に興味がある場合は、すばらしい本「CLR via C#」を読んでください。

オブジェクトの割り当てが非常に広範囲に及ぶ場合、オブジェクトの復活メカニズムを使用できますが、これにより、評価する必要がある多くの複雑さが追加され、多くの場合、支払う代償が大きくなります。次のようなファクトリ メソッドから開始できます。

MyObject obj = ObjectFactory.Allocate();

単純な代わりに:

MyObject obj = new MyObject();

このようにして、本当に必要な場合は別のものに切り替えることができますが...
...ちょっとしたヒント:自分が何をしているのか本当によくわからない場合や現在のメモリ割り当て戦略。

(このメッセージにはさらに大きなフォントを使用したくなる)

これは、アプリケーションを遅くし、コードが読みにくくなるため、アプリケーションに対して実行できる最悪のことの 1 つになる可能性があります。アプリケーションの 99.999% はこれらのカスタム要素を必要としません。あなたのアプリケーションが本当に必要としますか?

編集
例から、あなたが何をしているのかははっきりしていません。Alloc メソッドは ID を返しますが、割り当てられたデータを取得するにはどうすればよいでしょうか? とにかく...
本当にそのようなことをする必要があるなら...

  • バイトのリストを保持しないでください。メモリを浪費するだけです。
  • メソッドを提供しないFreeでください。あなたは .NET にいるので、GC に頼ってください。
  • 利用可能なブロック (Blockオブジェクト)のリストを保持します。このAllocateメソッドでは、空きブロックのリストから目的のサイズのブロックを検索します。見つかった場合は、そのブロックを返し、リストから削除します。ブロックが見つからない場合は、ブロックを割り当てて、単に呼び出し元に返す必要があります。
  • オブジェクトのファイナライザーでBlockGC.ReRegisterForFinalize メソッドを呼び出し、使用可能なブロックのリスト内にオブジェクトを挿入します。

非常に単純な実装です。例として、真のプログラムではないと考えてください。

sealed class Block
{
    internal Block(int size)
    {
        Data = new byte[size];
    }

    ~Block()
    {
        BlockFactory.Free(this);
        GC.ReRegisterForFinalize(this);
    }

    public byte[] Data
    {
        get;
        private set;
    }
}

static class BlockFactory
{
    public static Block Allocate(int size)
    {
        lock (_freeBlocks)
        {
            foreach (Block block in _freeBlocks)
            {
                if (block.Data.Length == size)
                {
                    _freeBlocks.Remove(block);

                    return block;
                }
            }

            return new Block(size);
        }
    }

    internal static void Free(Block block)
    {
        lock (_freeBlocks) _freeBlocks.Add(block);
    }

    private static List<Block> _freeBlocks = new List<Block>();
}

その点に注意してください:

  • この実装はまったく効率的ではありません (この場合、より適切な解決策は のReadWriterLockSlim代わりに を使用するlockか、 の代わりに別のより適切なデータ構造を使用する ことができますList<T>)。
  • 列挙を使用した検索はひどいものですが、ここではわかりやすくするために説明します。
  • 各オブジェクトにファイナライザーを追加すると、パフォーマンスが低下する場合があります。
  • この例ではBlock、必要なデータ (バイトの配列) のコンテナーとして使用します。これはあなたが必要とするものですか?

とはいえ、これに時間を費やす前に、必要かどうかを確認する必要があると私はまだ考えています. あなたのアプリケーションはこの問題に悩まされていますか? それは問題ですか?たとえば、データ処理アプリケーションがあるとします。パイプラインは次のステージで構成されています。

  • 取得 (一定の間隔でデータを取得するための何らかのタイミング)。
  • 処理(各種フィルター)。
  • 視覚化。

各パケットに新しいバッファを割り当てると、多数の小さなオブジェクトが作成される可能性があります。これが問題になるとは思いませんが、すべてのアプリケーションを複雑にするのではなく、取得段階で同じ (事前に割り当てられた) バッファーを再利用することを検討してください。

私が言いたいことが明確であることを願っています。

于 2012-04-17T07:39:15.490 に答える