Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Large data set with DynamicBuffer

Discussion in 'Entity Component System' started by xman7c7, Nov 3, 2018.

  1. xman7c7

    xman7c7

    Joined:
    Oct 28, 2016
    Posts:
    28
    Hello,

    is it right to store large data set using DynamicBuffer? I mean size about 65k or more items. Or better will be stored values separate per entity?

    I'm using multiple entities with component (DataComponent) which inside is large buffer array. When i try to iterate entities inside job using ComponentGroup, almost all entities are inside one chunk. Whatever i increase size buffer array all is same. Only if i change number of entities. Is this correct behaviour?

    Code (CSharp):
    1. public struct DataComponent: IBufferElementData
    2. {
    3.     public float3 Value;
    4. }
    Thanks
     
    Mr-Mechanical likes this.
  2. Derebeyi

    Derebeyi

    Joined:
    Jun 3, 2015
    Posts:
    7
    Each chunk can hold 16kb of data. If you exceed that it throws an error I believe. A float3 is probably 12 byte. If you store excessive amounts you'll end up with handful of entities per chunk.
    I use 'child knows parent, but parent does not know children' approach to not end up with situations like yours.
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,624
    You can use DynamicBuffer larger than 16k, you just have to size after entity is initialized.
    The max size of dynamic buffer i believe is int.maxvalue which is 2GB on most platforms (where size means length * sizeof<T>)

    I don't actually think DynamicBuffers are actually stored within the chunk, I think only the pointer to the actual memory allocation is.
     
    Last edited: Nov 3, 2018
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    It’s stores like IComponentData in chunk, but can allocate a heap memory block if the internal capacity is exhausted.
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,624
    Oh that's good to know. I originally speculated in that reply that might happen but edited and removed it.

    My guess was that it allocated in chunk on entity creation (hence 16kb limit) but any resize on the capacity will then cause it to be allocated on the heap (because looking at source it just seems to use the general memory allocation without any knowledge of chunks.)

    Is that correct?

    But then would using setbuffer reallocate within the chunk?
     
  6. xman7c7

    xman7c7

    Joined:
    Oct 28, 2016
    Posts:
    28
    So basically DynamicBuffers can be as long as you have memory, right?
    What about CPU cache miss? Is it affected be cache? or that different

    What about iterating data inside job? I did some test and if you have a lot of entities with large size of dynamicBuffers inside is better to create multiple jobs per entity. Instead of chunk iteration. I don't know is it right solution.
    On my PC its like:
    • IterateChunkSystem avg 310ms
    • IterateJobSystem avg 211ms

    Code (CSharp):
    1.  
    2. public static class Settings
    3. {
    4.     public static readonly int Size = 256 * 256;
    5.     public static readonly int Chunks = 1000;
    6. }
    7.  
    8. public struct TestComponent : IBufferElementData
    9. {
    10.     public float3 Value;
    11. }
    12.  

    Code (CSharp):
    1.  
    2. [DisableAutoCreation]
    3. public class CreateSystem : ComponentSystem
    4. {
    5.     [BurstCompile]
    6.     private struct Job : IJobParallelFor
    7.     {
    8.         [ReadOnly, DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
    9.         public ArchetypeChunkBufferType<TestComponent> TestComponent;
    10.         [ReadOnly] public int Size;
    11.  
    12.         public void Execute(int index)
    13.         {
    14.             ArchetypeChunk chunk = Chunks[index];
    15.             BufferAccessor<TestComponent> testComponent = chunk.GetBufferAccessor(TestComponent);
    16.             for (int i = 0; i < chunk.Count; i++)
    17.             {
    18.                 testComponent[i].ResizeUninitialized(Size);
    19.             }
    20.         }
    21.     }
    22.  
    23.     private ComponentGroup _group;
    24.  
    25.     protected override void OnCreateManager()
    26.     {
    27.         base.OnCreateManager();
    28.         EntityArchetype entityArchetype = EntityManager.CreateArchetype(typeof(TestComponent));
    29.         NativeArray<Entity> entities = new NativeArray<Entity>(Settings.Chunks, Allocator.Temp);
    30.         EntityManager.CreateEntity(entityArchetype, entities);
    31.  
    32.         _group = GetComponentGroup(typeof(TestComponent));
    33.     }
    34.  
    35.     protected override void OnUpdate()
    36.     {
    37.         NativeArray<ArchetypeChunk> chunk = _group.CreateArchetypeChunkArray(Allocator.TempJob);
    38.         new Job
    39.         {
    40.             Chunks = chunk,
    41.             TestComponent = GetArchetypeChunkBufferType<TestComponent>(),
    42.             Size = Settings.Size
    43.         }.Schedule(chunk.Length, 64).Complete();
    44.     }
    45. }
    46.  

    Code (CSharp):
    1.  
    2. [DisableAutoCreation]
    3. public class IterateChunkSystem : ComponentSystem
    4. {
    5.     [BurstCompile]
    6.     private struct Job : IJobParallelFor
    7.     {
    8.         [ReadOnly, DeallocateOnJobCompletion] public NativeArray<ArchetypeChunk> Chunks;
    9.         public ArchetypeChunkBufferType<TestComponent> TestComponent;
    10.  
    11.         public void Execute(int index)
    12.         {
    13.             ArchetypeChunk chunk = Chunks[index];
    14.             BufferAccessor<TestComponent> testComponent = chunk.GetBufferAccessor(TestComponent);
    15.             for (int i = 0; i < chunk.Count; i++)
    16.             {
    17.                 DynamicBuffer<float3> data = testComponent[i].Reinterpret<float3>();
    18.                 for (int j = 0; j < data.Length; j++)
    19.                 {
    20.                     data[j] = new float3(1f);
    21.                 }
    22.             }
    23.         }
    24.     }
    25.  
    26.     private ComponentGroup _group;
    27.  
    28.     protected override void OnCreateManager()
    29.     {
    30.         base.OnCreateManager();
    31.         _group = GetComponentGroup(typeof(TestComponent));
    32.     }
    33.  
    34.     protected override void OnUpdate()
    35.     {
    36.         NativeArray<ArchetypeChunk> chunk = _group.CreateArchetypeChunkArray(Allocator.TempJob);
    37.         new Job
    38.         {
    39.             Chunks = chunk,
    40.             TestComponent = GetArchetypeChunkBufferType<TestComponent>(),
    41.         }.Schedule(chunk.Length, 64).Complete();
    42.     }
    43. }
    44.  

    Code (CSharp):
    1.  
    2. [DisableAutoCreation]
    3. public class IterateJobSystem : ComponentSystem
    4. {
    5.     [BurstCompile]
    6.     private unsafe struct Job : IJobParallelFor
    7.     {
    8.         [NativeDisableUnsafePtrRestriction] public TestComponent* TestComponent;
    9.  
    10.         public Job(DynamicBuffer<TestComponent> testChunk)
    11.         {
    12.             TestComponent = (TestComponent*) testChunk.GetBasePointer();
    13.         }
    14.  
    15.         public void Execute(int index)
    16.         {
    17.             TestComponent item = TestComponent[index];
    18.             item.Value = new float3(1f);
    19.             TestComponent[index] = item;
    20.         }
    21.     }
    22.  
    23.     private ComponentGroup _group;
    24.  
    25.     protected override void OnCreateManager()
    26.     {
    27.         base.OnCreateManager();
    28.  
    29.         _group = GetComponentGroup(typeof(TestComponent));
    30.     }
    31.  
    32.     protected override void OnUpdate()
    33.     {
    34.         NativeArray<ArchetypeChunk> chunks = _group.CreateArchetypeChunkArray(Allocator.TempJob);
    35.         NativeArray<JobHandle> jobHandles = new NativeArray<JobHandle>(Settings.Chunks, Allocator.Persistent);
    36.         ArchetypeChunkBufferType<TestComponent> archtypeTestComponent = GetArchetypeChunkBufferType<TestComponent>();
    37.         int counter = 0;
    38.  
    39.         for (int i = 0; i < chunks.Length; i++)
    40.         {
    41.             ArchetypeChunk chunk = chunks[i];
    42.             BufferAccessor<TestComponent> bufferAccessor = chunk.GetBufferAccessor(archtypeTestComponent);
    43.             for (int j = 0; j < bufferAccessor.Length; j++)
    44.             {
    45.                 DynamicBuffer<TestComponent> dynamicBuffer = bufferAccessor[j];
    46.                 jobHandles[counter] = new Job(dynamicBuffer).Schedule(dynamicBuffer.Length, 64);
    47.                 counter++;
    48.             }
    49.         }
    50.  
    51.         JobHandle.CompleteAll(jobHandles);
    52.         jobHandles.Dispose();
    53.         chunks.Dispose();
    54.     }
    55. }
    56.  
     
    KAV2008 and Derebeyi like this.
  7. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    Yep, it allocates memory in chunk on creation (this why buffer has InternalBufferCapacity, for define how much memory will be allocated in chunk) all outsize allocated in heap and: “Memory management is fully automatic when using this approach. Memory associated withDynamicBuffers is managed by the EntityManager so that when a DynamicBuffercomponent is removed, any associated heap memory is automatically freed as well.”
    About setbuffer I think logic same - when setbuffer it fill allocated memory in chunk and if not enough then allocates more on heap.
     
  8. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    IterateChunkSystem is the current fastest API, but we would like to introduce something more akin to IJobProcessComponentData as well. Not sure yet when we will do it...

    Your comparison to IterateJobSystem is doing something totally different so don't think is valid perf comparisons.

    Yes uses UnsafeUtility.Malloc as fallback when exceeding the user defined default capacity. So until you run out of memory in that process.

    Default capacity comes from chunk data. That also explains how to think about cache misses for those arrays.
    As long as the arrays are small iterating over multiple entities with buffers will have linear memory access and no pointer jumps. If the user defined default capacity is exceeded each buffer access is a random pointer location, so effectively cache miss. In practice this is a very nice tradeoff since if you have large amounts of buffer memory then paying for one cache miss at the beginning is not a big deal overall. While lots of small buffers but many entities it is important to make sure you don't have cachemiss on each entity buffer...
     
    LevonVH and eizenhorn like this.
  9. xman7c7

    xman7c7

    Joined:
    Oct 28, 2016
    Posts:
    28
    How do i know if i have cache miss?
     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    2,653
    Watch to chunk diagram in EntityDebugger, and...you must understand your data, which size is require, which data you request, you must remember about chunk size and struct your data with minimal cache misses.