StructureOfArraysGenerator

repository·main·Indexed 43 days ago

https://github.com/cysharp/structureofarraysgenerator

A C# source generator that transforms Arrays of Structures (AoS) into Structure of Arrays (SoA) to improve CPU cache efficiency and enable SIMD parallel processing. Version 1.1.0 provides the [MultiArray] attribute to generate structs backed by a single byte array with Span<T> properties for each field. Supports standard .NET projects via NuGet and Unity 2021.3+ via UPM.

Tokens
1.7K
Snippets
6
Records
7
Agent score
6%

What's inside StructureOfArraysGenerator

  1. Install StructureOfArraysGenerator via NuGet

    main

    For standard .NET projects, install the library using the NuGet Package Manager.

    Requirements:

    • Roslyn 4.3.1 support (e.g., Visual Studio 2022 version 17.3, .NET SDK 6.0.401).
    • The package provides only the analyzer; the generated code has no external dependencies.
    PM> Install-Package StructureOfArraysGenerator
  2. Use MultiArrayList for expandable SoA

    main

    If you need a structure that can grow dynamically (like List<T>), use the MultiArrayList functionality. You enable this by adding the MultiArrayList argument to the [MultiArray] attribute.

    This generates a companion ***MultiArrayList class that provides an Add method and manages capacity automatically.

    [MultiArray(typeof(Vector3), MultiArrayList)]
    public readonly partial struct Vector3MultiArray
    {
    }
    
    // Usage
    var list = new Vector3MultiArrayList();
    list.Add(new Vector3(1, 2, 3));
    
    var zeroX = list.X[0];
    
    // Convert to a fixed-size MultiArray
    Vector3MultiArray array = list.ToArray();
  3. Install StructureOfArraysGenerator in Unity

    main

    For Unity projects, the installation is different from NuGet. You can install via UPM git URL or an asset package.

    UPM Installation: Use the following URL in the Unity Package Manager: https://github.com/Cysharp/StructureOfArraysGenerator.git?path=src/StructureOfArraysGenerator.Unity/Assets/Plugins/StructureOfArraysGenerator

    You can pin a specific version by appending #version (e.g., #1.0.0).

    Requirements:

    • Minimum Unity version: 2021.3.
    • If using the Git URL, you must manually provide the System.Runtime.CompilerServices.Unsafe DLL (available via NuGet) as it is not included in the Git repository to avoid unnecessary dependencies.
  4. Quick Start: Create a MultiArray

    main

    To generate a Structure of Arrays (SoA) structure, define a readonly partial struct and decorate it with the [MultiArray] attribute, passing the type you want to decompose.

    This generates a struct backed by a single byte[] that provides Span<T> properties for each field of the target struct, allowing for high-performance, cache-friendly access.

    using StructureOfArraysGenerator;
    
    [MultiArray(typeof(Vector3))]
    public readonly partial struct Vector3MultiArray
    {
    }
  5. Serialize MultiArray with MemoryPack

    main

    Since MultiArray is a custom structure, it is not supported by MemoryPack by default. To enable fast serialization, you must implement a custom MemoryPackFormatter<T> and register it at startup.

    internal sealed class MultiArrayFormatter<T> : MemoryPackFormatter<T>
        where T : struct, IMultiArray<T>
    {
        public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref T value)
        {
            writer.WriteUnmanaged(value.Length);
            writer.WriteUnmanagedSpan(value.GetRawSpan());
        }
    
        public override void Deserialize(ref MemoryPackReader reader, scoped ref T value)
        {
            var length = reader.ReadUnmanaged<int>();
            var array = reader.ReadUnmanagedArray<byte>();
            value = T.Create(length, array!);
        }
    }
    
    // Registration
    MemoryPackFormatterProvider.Register(new MultiArrayFormatter<Vector3MultiArray>());
  6. How to use MultiArray

    main

    A MultiArray behaves similarly to a standard array T[] but provides field-specific Span<T> views for SIMD-friendly processing.

    Key Usage Patterns:

    • Field Access: Use properties named after the struct members (e.g., .X, .Y) to get a Span<T> of that specific field.
    • Indexing: Use the indexer array[index] to get/set the full struct.
    • Iteration: Use foreach to iterate over the full structs, or foreach over a specific field's Span<T> for high-performance aggregate operations.
    • Memory Management: To avoid byte[] allocations, use the constructor that accepts an ArraySegment<byte> combined with ArrayPool<byte>.
    var array = new Vector3MultiArray(4);
    
    // Set individual field via Span
    array.X[0] = 10;
    
    // Set full struct via indexer
    array[1] = new Vector3(1.1f, 2.2f, 3.3f);
    
    // High-performance field iteration (SIMD friendly)
    foreach (ref var item in array.Y)
    {
        item *= 2;
    }
    
    // Full struct iteration
    foreach (var item in array)
    {
        Console.WriteLine($"{item.X}, {item.Y}, {item.Z}");
    }
  7. Configure MultiArrayAttribute options

    main

    The [MultiArray] attribute allows you to customize which members are included in the SoA structure.

    Overloads:

    • [MultiArray(Type type)]: Includes all public or internal fields/properties by default.
    • [MultiArray(Type type, bool includeProperty = false)]: Set includeProperty: true to include properties in the SoA.
    • [MultiArray(Type type, params string[] members)]: Specify exactly which members should have corresponding Span<T> arrays generated.

    Constraints:

    • All target members must be unmanaged types (structs containing no reference types).
    • The target struct should be readonly for best performance; use the in keyword when passing MultiArray to methods to avoid copying.
    // Include properties
    [MultiArray(typeof(MyStruct), includeProperty: true)]
    
    // Include only specific members
    [MultiArray(typeof(MyStruct), "X", "Y")]