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 应用程序确实需要自定义内存管理器,则不应遵循非托管世界(您的第二个链接)的提示(或简单地翻译代码)。

.NET 环境中的内存分配完全不同,内存碎片更多(因为默认分配器赋予分配速度特权)但它可以被压缩(因此内存碎片问题在那里并不是真正的问题)。

这不是你的情况,但是大对象(现在这个阈值设置为 85 KB)将使用不同的策略进行分配,并且它们不会被压缩。我认为只有当你创建了很多短暂的大对象时,你才可能需要一个自定义分配器。

第一个链接提供了一个非常幼稚的实现,您是否在多线程环境中对其进行了分析在您的情况下,您确定它的性能优于默认分配吗?即使它表现得更好一点,你确定你需要它吗?

为了使您的内存分配器线程安全,您可以为每个线程使用不同的堆或简单地锁定您的数据结构(例如,如果您将空闲内存块列表保留在 LinkedList 中,您可以在从列表中删除节点时锁定结构)。这不是几行就可以解释的话题,如果你真的对这些内部原理感兴趣,你可以阅读伟大的“CLR via C#”一书。

当对象分配非常广泛时,您可以为对象使用复活机制,但这会增加许多必须评估的复杂性,通常您要付出的代价更大。您可以从工厂方法开始,例如:

MyObject obj = ObjectFactory.Allocate();

而不是简单的:

MyObject obj = new MyObject();

通过这种方式,如果你真的需要它,你可以切换到其他东西,但是
............一个小提示:如果你不确定你在做什么并且在你分析当前的之后,不要玩内存分配内存分配策略。

(我很想为这条消息使用更大的字体)

这可能是您可以对您的应用程序做的最糟糕的事情之一,因为您会使它变慢并且您的代码将不那么可读。99.999% 的应用程序不需要这些自定义的东西,你确定你的应用程序需要吗?

编辑
从示例中并不清楚您在做什么。您的 Alloc 方法返回一个 ID,但您如何获取分配的数据?无论如何......
如果你真的需要做那样的事情......

  • 不要保留字节列表,只会浪费内存。
  • 不提供Free方法,您在.NET 中,所以请依赖 GC。
  • 保留可用块(Block对象)的列表。在该Allocate方法中,您将在空闲块列表中搜索所需大小的块。如果找到它,则返回该块并将其从列表中删除。如果您没有找到该块,您必须分配它并将其简单地返回给调用者。
  • Block对象的终结器中调用 GC.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 回答