【问题标题】:How To Allocate Contiguous and Fixed Memory in .NET Core如何在 .NET Core 中分配连续和固定的内存
【发布时间】:2019-10-25 21:48:32
【问题描述】:

我正在从事一个性能至关重要的物理模拟项目,我认为一个瓶颈是我的内存管理。目前,我有包含固定数量的刚体、粒子和力的缓冲对象。在模拟开始之前,所有物理实体都在其缓冲区中初始化。当需要一个实体时,选择第一个不活动的实体,否则,最旧的,当不再需要时,将其移到最后,因此活动实体都放在前面。

这是我正在使用的数据结构。

public sealed class Buffer<TValue> : IEnumerable<BufferElement<TValue>> where TValue : new()
{
    public Buffer(int capacity)
    {
        Count = 0;
        Capacity = capacity;
        Elements = new BufferElement<TValue>[capacity];

        for (var index = 0; index < Elements.Length; index++)
        {
            Elements[index] = new BufferElement<TValue>();
        }
    }

    public int Count { get; private set; }

    public int Capacity { get; private set; }

    private int ActiveCount { get; set; }

    private BufferElement<TValue>[] Elements { get; }

    public BufferElement<TValue> Activate()
    {
        if (Count == ActiveCount) Count = 0;

        var bufferElement = Elements[Count++];

        if (!bufferElement.Active)
        {
            bufferElement.Active = true;
            ActiveCount++;
        }

        return bufferElement;
    }

    public void Deactivate(BufferElement element)
    {
        if (!element.Active) return;

        element.Active = false;

        var lhs = element.Index;
        var rhs = --ActiveCount;

        Elements[lhs] = Elements[rhs];
        Elements[rhs] = element;

        Elements[lhs].Index = lhs;
        Elements[rhs].Index = rhs;
    }
}

在阅读了 .NET Core 如何处理数组之后,有两件事可能是个问题。第一个是每次访问数组中的元素时,它都会进行安全检查,第二个是GC可以将数组复制到一个新的内存地址。

如果可能,我希望所有包含物理实体的缓冲区不执行任何安全检查并固定在连续内存中。我相信这应该是可能的,因为每个缓冲区的大小是固定的,元素(刚体、粒子、力)的大小也是固定的。

在 C# 中管理内存的方法似乎有很多,我很难确定在这种情况下哪种方法适合我。

现在,问题归结为三个部分:

  1. 可以这样做吗?
  2. 如果是这样,管理内存的最佳方法是什么?
  3. 那么,正确的实现应该是什么样的?

【问题讨论】:

  • 考虑查看 c# 的 Span。这是 c# 中高性能的数组的线程安全包装器。 youtube.com/watch?v=NVWQRbqcXJ4
  • 在我的情况下,我应该使用 Memory 吗?模拟是长时间运行的,所以它会占用一个以上的堆栈帧。如果我理解正确的话,Span 是为了在堆栈上分配空间,当执行离开上下文时释放。
  • 您可以在静态空间中分配它,这样它就不会超出范围并被丢弃。
  • 是否有理由使用 Span 而不是 Memory
  • array 布局在连续内存中。 contents 可能不是:由于您正在修改BufferElement,我假设它是一个类,此时数组将包含指针,而不是元素本身。我不清楚结构是否会直接放入数组中,但如果是这样,它很可能会受到与直接复制优化类似的约束。仅当您(按比例)对顺序元素执行足够量的工作时,顺序布局才有用 - 如果将元素直接放置到数组中,这会容易得多。

标签: c# memory-management .net-core


【解决方案1】:

首先,是的,可以做到。其次,最佳确实取决于,因为存在许多权衡,这很快就变成了有意见的帖子。第三,最简单的替代方法是使用 fixed 关键字在 unsafe 上下文中分配您的数组(此处既不对数组进行安全检查,也允许您使用 C 样式指针)并固定其地址,使其不会'不会改变,虽然使用SpanMemory 可能更容易,但fixedunsafe 的低级方法在正确使用时可以产生更好的性能。查看this,它是官方文档,里面充满了简洁的示例。最后一个提示,尝试切换到struct,而不是如果可能,它们没有内存开销和更高的内存密度,产生更好的缓存访问时间,因为缓存中可以容纳更多。

【讨论】:

  • 如果你有一个struct的数组,访问器不会复制值吗?我使用类的原因是因为刚体、粒子和力可以有一对多的关系,而且我的假设是引用一个对象会更快。我假设如果我要使用结构,我需要存储一个指向元素的数组而不是一个元素数组?最后,用Memory&lt;T&gt; 中的.Pin() 代替使用fixed 不够吗?
  • 这就是为什么我说“如果可能的话”。但是,struct 的数组作为引用传递。数组是引用类型,即使它们包含值类型。此外,请记住您可以使用 ref 关键字来传递对值类型的引用并避免复制。
  • 是的,但SpanMemory 的级别高于fixedunsafe。我以为你想要尽可能多的原始性能和控制。
  • 我的意思是访问像array[index] 这样的数组会在索引处产生一个值的副本。另外,我不知道你可以从函数中返回ref,我认为这仅适用于入站参数。
  • GC 将控制内存,fixed 向 GC 指示特定变量不能移动到另一个内存位置。从 C# 7 开始,有一个名为 ref return 的功能,您可以将 ref array[index] 传递给具有入站 ref 的方法,并且该特定值通过引用传递。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-16
  • 2019-10-23
相关资源
最近更新 更多