C# 中具有固定大小数组的连续分层结构内存?

Posted

技术标签:

【中文标题】C# 中具有固定大小数组的连续分层结构内存?【英文标题】:Contiguous hierarchical struct memory with fixed-size arrays in C#? 【发布时间】:2018-04-11 16:54:50 【问题描述】:

我有一个任务,在 C 中是微不足道的,但 C# 似乎(故意?)不可能。

在 C 语言中,我会预先分配我的模拟的整个数据模型,通过设置为单个整体层次结构的结构,包括更多结构的固定大小数组,可能包含更多数组。这在 C# 中几乎是可行的,除了一件事......

在 C# 中,我们使用 fixed 关键字在每个结构类型中指定固定大小的缓冲区(数组) - Cool。然而,这只支持原语作为固定缓冲区元素类型,在这些工作中抛出了一个主要的扳手,即拥有一个单一的、分层的和连续分配的数据模型,开始确保最佳的 CPU 缓存访问。

我可以看到的其他方法如下:

    使用通过单独的new 将数组分配到别处的结构(这似乎完全破坏了连续性)- 标准做法,但效率不高。 使用原始类型的固定数组(比如byte),但是当我想改变事物时必须来回编组这些……这会很容易工作吗?可能会非常乏味。 做 (1),同时假设平台知道移动物体以获得最大的连续性。

我在 Unity 5.6 下使用 .NET 2.0。

【问题讨论】:

听起来你想要一种不同的语言,例如 C 或 C++ 【参考方案1】:

请查看 C# 7.2 的 Span<T>Memory<T> 功能。我想这会解决你的问题。

What is the difference between Span<T> and Memory<T> in C# 7.2?

【讨论】:

我不能使用 C# 7.2。我正在使用 .NET 2.0 AFAIK - Unity。请删除答案,因为它无关紧要?我已经更新了问题。 如果您在 Unity 中使用 .Net 标准 2.0,那么它可能是 C# 6 或 7,并且可能有用于这些功能的 nuget 包。请看forum.unity.com/threads/c-7-support.512661 我看到的另一种方法是在 C++ dll 中创建该逻辑,然后使用一些 P/Invoke 从 C# 访问。 没错,但非常乏味。我想这是方便与性能的对比。 顺便说一句:我现在太费心了,但我偶然发现了this hack to allow C# 7.2 features in Unity。【参考方案2】:

无法访问Memory&lt;T&gt;,最终选择了选项(2),但不需要编组,只需强制转换:在unsafe struct 中使用fixed 字节数组,并按如下方式向/从这些字节进行转换:

using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
    
public class TestStructWithFixed : MonoBehaviour

    public const int MAX = 5;
    public const int SIZEOF_ELEMENT = 8;
    
    public struct Element
    
        public uint x;
        public uint y;
        //8 bytes
    
    
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public unsafe struct Container
    
        public int id; //4 bytes
        public unsafe fixed byte bytes[MAX * SIZEOF_ELEMENT];
    
    
    public Container container;
    
    void Start ()
    
        Debug.Log("SizeOf container="+Marshal.SizeOf(container));
        Debug.Log("SizeOf element  ="+Marshal.SizeOf(new Element()));
        
        unsafe
        
            Element* elements;
            fixed (byte* bytes = container.bytes)
            
                elements = (Element*) bytes;
                
                //show zeroed bytes first...
                for (int i = 0; i < MAX; i++)
                    Debug.Log("i="+i+":"+elements[i].x);
                
                //low order bytes of Element.x are at 0, 8, 16, 24, 32 respectively for the 5 Elements
                bytes[0 * SIZEOF_ELEMENT] = 4;
                bytes[4 * SIZEOF_ELEMENT] = 7;
            
            elements[2].x = 99;
            //show modified bytes as part of Element...
            for (int i = 0; i < MAX; i++)
                Debug.Log("i="+i+":"+elements[i].x); //shows 4, 99, 7 at [0], [2], [4] respectively
        
    

unsafe 访问速度非常快,而且没有编组或复制 - 这正是我想要的。

如果可能对所有struct 成员使用4 字节ints 或floats,您甚至可以更好地将fixed 缓冲区基于这种类型(uint 始终一个干净的选择) - 易于调试。


2021 年更新

今年我重新讨论了这个主题,在 Unity 5 中进行原型设计(由于编译/迭代时间快)。

坚持使用一个非常大的字节数组并在托管代码中使用它会更容易,而不是使用fixed + unsafe(顺便说一下,从 C# 7.3 开始,it is no longer necessary to use the fixed keyword every time to pin a fixed-size buffer 才能访问它) .

fixed 我们失去了类型安全性;这是互操作数据的一个自然缺点——无论是原生数据还是托管数据的互操作; CPU和GPU;或 Unity 主线程代码和用于新 Burst / Jobs 系统的代码之间。这同样适用于托管字节缓冲区。

因此,可以更轻松地接受使用无类型托管缓冲区并自己编写偏移量 + 大小。 fixed / unsafe 提供(一点)更多便利,但不是很多,因为您同样必须指定编译时结构字段偏移量,并在每次数据设计更改时更改这些偏移量。至少对于托管 VLA,我可以对代码中的偏移量求和,但这确实意味着这些不是编译时常量,因此会失去一些优化。

与托管 VLA(在 Unity 中)相比,以这种方式分配 fixed 缓冲区的唯一真正好处是,对于后者,GC 有可能在播放过程中将您的整个数据模型移动到其他地方,这可能会导致打嗝,尽管我还没有看到这在生产中有多严重。

托管数组are not, however, directly supported by Burst。

【讨论】:

其他对此主题感兴趣的人,另请参阅this very informative article。

以上是关于C# 中具有固定大小数组的连续分层结构内存?的主要内容,如果未能解决你的问题,请参考以下文章

数据结构

数据结构概括

数据结构概览

源码:Java集合源码之:数组与链表

go——数组

408考研操作系统)第三章内存管理-第一节4:连续分配管理方式(单一连续固定分区和动态分区分配)