2

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

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

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

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

  1. 使用通过单独new(这似乎完全破坏连续性)在其他地方分配数组的结构 - 标准做法但效率不高。
  2. 使用原始类型的固定数组(例如byte),但是当我想改变事物时必须来回编组它们......这甚至可以轻松工作吗?可能非常乏味。
  3. 做(1),同时假设平台知道移动东西以获得最大的连续性。

我在 Unity 5.6 下使用 .NET 2.0。

4

2 回答 2

3

请看一下C# 7.2 的特性Span<T>Memory<T>特性。我认为这会解决你的问题。

C# 7.2 中的 Span<T> 和 Memory<T> 有什么区别?

于 2018-04-11T16:59:14.437 回答
1

如果无法访问Memory<T>,最终选择了选项(2),但不需要编组,只需强制转换:fixed在 an 中使用字节数组unsafe struct并强制转换为/从这些中转换,如下所示:

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访问速度非常快,并且没有编组或副本 - 这正是我想要的。

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


2021 年更新

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

坚持使用一个非常大的字节数组并在托管代码中使用它会更容易,而不是使用fixed+ unsafe(顺便说一下,从 C# 7.3开始,不再需要fixed每次都使用关键字来固定固定大小的缓冲区为了访问它)。

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

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

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

但是,Burst 不直接支持托管阵列。

于 2018-04-12T20:42:15.527 回答