[英]Contiguous hierarchical struct memory with fixed-size arrays in C#?
I have a task which in C would be trivial but which C# seems to make (intentionally?) impossible.我有一个任务,在 C 中是微不足道的,但 C# 似乎(故意?)不可能。
In CI would pre-allocate the entire data model of my simulation, via structs set up as a single, monolithic hierarchy, including fixed-size arrays of yet more structs, maybe containing more arrays.在 CI 中,将通过设置为单个整体层次结构的结构预先分配我的模拟的整个数据模型,包括更多结构的固定大小数组,可能包含更多数组。 This is nigh-doable in C#, except for one thing...
这在 C# 中几乎是可行的,除了一件事......
In C#, we have the fixed
keyword to specify fixed-size buffers (arrays) in each struct type - Cool.在 C# 中,我们有
fixed
关键字来指定每个结构类型中固定大小的缓冲区(数组) - Cool。 However, this supports only primitives as the fixed buffer element type, throwing a major spanner in these works of having a single monolithic, hierarchical and contiguously-allocated data model that begins to ensure optimal CPU cache access.然而,这仅支持原语作为固定缓冲区元素类型,在这些工作中抛出了一个主要的扳手,即拥有一个单一的、分层的和连续分配的数据模型,开始确保最佳的 CPU 缓存访问。
Other approaches I can see are the following:我可以看到的其他方法如下:
new
(which would seem to defeat contiguity entirely) - standard practice but not efficient.new
(这似乎完全破坏连续性)在别处分配数组的结构 - 标准做法但效率不高。byte
) but then have to marshal these back and forth when I want to change things... will this even work easily?byte
),但是当我想改变事物时必须来回编组这些......这甚至会很容易工作吗? Could be very tedious. I am using .NET 2.0 under Unity 5.6.我在 Unity 5.6 下使用 .NET 2.0。
Please take a look on Span<T>
and Memory<T>
features of C# 7.2.请查看 C# 7.2 的
Span<T>
和Memory<T>
特性。 I think that would solve your problem.我认为这会解决你的问题。
What is the difference between Span<T> and Memory<T> in C# 7.2? C# 7.2 中的 Span<T> 和 Memory<T> 有什么区别?
Without access to Memory<T>
, ended up going with option (2), but no marshalling was necessary, only casting: use a fixed
array of bytes in an unsafe struct
and cast to/from these as follows:无法访问
Memory<T>
,最终选择了选项 (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
access is very fast, and with no marshalling or copies - is exactly what I wanted. unsafe
访问速度非常快,并且没有编组或副本 - 正是我想要的。
If likely to be using 4-byte int
s or float
s for all your struct
members, you might even do better to base your fixed
buffer off such a type ( uint
is always a clean choice) - readily debuggable.如果可能对所有
struct
成员使用 4 字节int
s 或float
s,您甚至可以更好地将fixed
缓冲区基于这种类型( uint
始终是一个干净的选择) - 易于调试。
UPDATE 2021 2021 年更新
I've revisited this topic this year, for prototyping in Unity 5 (due to fast compile / iteration times).今年我重新讨论了这个话题,在 Unity 5 中进行原型设计(由于快速编译/迭代时间)。
It can be easier to stick with one very large byte array, and use this in managed code, rather than bothering with fixed
+ unsafe
(by the way since C# 7.3 it is no longer necessary to use the fixed
keyword every time to pin a fixed-size buffer in order to access it).坚持使用一个非常大的字节数组会更容易,并在托管代码中使用它,而不是烦恼
fixed
+ unsafe
(顺便说一下,从 C# 7.3 开始,不再需要每次都使用fixed
关键字来固定一个固定的-size 缓冲区以访问它)。
With fixed
we lose type-safety;使用
fixed
我们失去了类型安全性; this being a natural shortcoming of interop data - whether interop between native and managed;这是互操作数据的一个自然缺点——本地和托管之间是否互操作; CPU and GPU;
CPU和GPU; or between Unity main thread code and that used for the new Burst / Jobs systems.
或者在 Unity 主线程代码和用于新 Burst / Jobs 系统的代码之间。 The same applies for managed byte buffers.
这同样适用于托管字节缓冲区。
Thus it can be easier to accept working with untyped managed buffers and writing offset + sizes yourself.因此,可以更容易地接受使用无类型的托管缓冲区并自己编写偏移量 + 大小。
fixed
/ unsafe
offers (a little) more convenience, but not by much, since you equally have to specify compile-time struct field offsets and change these each time the data design changes. fixed
/ unsafe
提供(一点)更多的便利,但不是太多,因为您同样必须指定编译时结构字段偏移量并在每次数据设计更改时更改这些偏移量。 At least with managed VLAs, I can sum offsets in code, however this does mean these are not compile-time constants, thus losing some optimisations.至少对于托管 VLA,我可以对代码中的偏移量求和,但这确实意味着这些不是编译时常量,因此会失去一些优化。
The only real benefit of allocating a fixed
buffer this way vs. a managed VLA (in Unity), is that with the latter, there is a chance the GC will move your entire data model somewhere else in mid-play, which could cause hiccups, though I've yet to see how serious this is in production.与托管 VLA(在 Unity 中)相比,以这种方式分配
fixed
缓冲区的唯一真正好处是,对于后者,GC 有可能在中途将您的整个数据模型移动到其他地方,这可能会导致打嗝,虽然我还没有看到这在生产中有多严重。
Managed arrays are not, however, directly supported by Burst . 但是, Burst 不直接支持托管阵列。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.