I have a task which in C would be trivial but which C# seems to make (intentionally?) impossible.
In C I 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. This is nigh-doable in C#, except for one thing...
In C#, we have the fixed
keyword to specify fixed-size buffers (arrays) in each struct type - 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.
Other approaches I can see are the following:
- Use structs that allocate the array elsewhere through a separate
new
(which would seem to defeat contiguity entirely) - standard practice but not efficient. - Use the fixed arrays of primitive types (say
byte
) but then have to marshal these back and forth when I want to change things... will this even work easily? Could be very tedious. - Do (1) while assuming that the platform knows to moves things around for maximum contiguity.
I am using .NET 2.0 under Unity 5.6.
Without access to
Memory<T>
, ended up going with option (2), but no marshalling was necessary, only casting: use afixed
array of bytes in anunsafe struct
and cast to/from these as follows:unsafe
access is very fast, and with no marshalling or copies - is exactly what I wanted.If likely to be using 4-byte
int
s orfloat
s for all yourstruct
members, you might even do better to base yourfixed
buffer off such a type (uint
is always a clean choice) - readily debuggable.UPDATE 2021
I've revisited this topic this year, for prototyping in Unity 5 (due to fast compile / iteration times).
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 thefixed
keyword every time to pin a fixed-size buffer in order to access it).With
fixed
we lose type-safety; this being a natural shortcoming of interop data - whether interop between native and managed; CPU and GPU; or between Unity main thread code and that used for the new Burst / Jobs systems. 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. 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.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.Managed arrays are not, however, directly supported by Burst.