Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

The cost of Dynamic Buffer

Discussion in 'Data Oriented Technology Stack' started by NoDumbQuestion, Jan 9, 2019.

  1. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    172
    As I understand
    IBufferElementData
    . It is the stuff that allow me to use array/list inside
    struct : IComponentData

    From my code, i reserve buffer capacity as 8 in chunk
    Code (CSharp):
    1.     public struct WayPoints : IComponentData
    2.     {
    3.         public float3 curPoint;
    4.         public int    curBufferIndex;
    5.         public int    remWayPoints;
    6.         public Bool   isArrive;
    7.         public float3 finalDestination;
    8.     }
    9.  
    10.     [InternalBufferCapacity(8)]
    11.     public struct WayPoint : IBufferElementData
    12.     {
    13.         public static implicit operator float3(WayPoint e) { return e.point; }
    14.         public static implicit operator WayPoint(float3 e) { return new WayPoint {point = e}; }
    15.         public                          float3 point;
    16.     }
    When I add it to Entity
    manager.AddBuffer<WayPoint>(entity);

    The Buffer length is 0 like in the gif. I am not sure if it was preallocate.



    So my guess is if I access/add the first 8 value in buffer it gonna be fast as it is inside the chunk memory. Any more than 8 will go to heap memory which is significant slower in both access/add/remove speed and should be avoid in Job?

    And I am not sure how much buffer I can pre-allocate before hand since I am not sure how many entity can be fit inside a chunk
     
    Last edited: Jan 9, 2019
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,569
    May I ask, what lead you to this conclusion? I mean, why 8 in particular?
    If you click entity holding buffer array, you will see chunk management.
     
  3. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    172
    I read from the doc

    For detail, instantiate Entity and call method
    EntityManager.AddBuffer<>
    . The Entity debugger show the Buffer of that entity as empty. Length = 0. (Sorry my gif did not show entity debugger screen).

    While I expect
    [InternalBufferCapacity(8)]
    will pre-allocate my buffer before hand that will give me an empty array of 8 empty value like an Array.

    So I was quite confuse what [InternalBufferCapacity] do.

    I think DynamicBuffer function like a list that you can Add/remove as you please. Except when it grow outside of internal capacity, those extra value go to heap memory where it take longer to access
     
  4. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,267
    Length and Capacity is different, capacity is size of buffer, length is count of elements in buffer. InternalBufferCapacity sets capacity and not length.

    upload_2019-1-9_14-33-31.png

    This not preallocate memory, because is null pointer, after assigning data pointer sets.

    upload_2019-1-9_14-35-56.png

    You allocate memory when you initialize DynamicBuffer and add elements to him. This call
    ResizeUninitialized which call method above (from BufferHeader)

    upload_2019-1-9_14-40-56.png
     
    Last edited: Jan 9, 2019
  5. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,569
    The chunk has 16k capacity by default. Doc states of using 8 elements capacity as an example. Multiply by size of integer, that is far below capacity of chunk. So you can easily preallocate bigger buffer arrays if you like. And you can add remove easily within desired capacity limit, without going into heap. Providing you know, or can predict max required size.

    If I can expected often add and remove elements to buffer, I avoid resizing it Dow. Only to grow. I just hold extra int, which tells me, how many elements I am using. So if I remove indexes from the top of array, I just reduce index, of next spare element. So I don't need physically remove elements from bufferarray, which could otherwise lead to capacity resize.
     
    Last edited: Jan 9, 2019
  6. NoDumbQuestion

    NoDumbQuestion

    Joined:
    Nov 10, 2017
    Posts:
    172
    You are right. I was confuse of the term. Thanks for point out. It make much more sense.

    I was thinking the same thing. But then i look inside BufferHeader Add and remove method.
    Annotation 2019-01-10 091229.jpg
    All it do is MemMove. Which I assume is quite negligible as long as it is within capacity. So I guess for convenient, we dont have to keep an index of buffer
     
    Antypodish likes this.
  7. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,569
    That interesting dig.
    Just to add to my approach (my not be suitable for every one), I tend remove elements only from the last index. In the sense of remove, I simply reduce my int index, rather use built in buffer index. I don't even need clear spare buffer element (s) value (s), since I know, It will be overwritten, if I decide to reuse it. Hence trying to avoid mem move. The reason for my approach is, that for big arrays I can suspect, mem move can be expensive. So I try avoid such operations.

    If that makes sense.
     
  8. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    1,247
    BufferArray will never shrink, only grow.

    It is something to note, because if you allocate a huge capacity for just a single frame then only use a tiny bit of it for the rest of it's life, it'll stay at the huge capacity till disposed.

    -edit-

    If you look into source where it resizse

    Code (CSharp):
    1.         public static void EnsureCapacity(BufferHeader* header, int count, int typeSize, int alignment, TrashMode trashMode)
    2.         {
    3.             if (header->Capacity >= count)
    4.                 return;
    If you try to shrink it just returns leaving the capacity unchanged.

    A side note on performance, if order is not important to you in a dynamic array, you can use this faster method, RemoveAtSwapBack(int index), which is based off the method of the same name from NativeList<T>

    Code (CSharp):
    1.         public static void RemoveAtSwapBack<T>(this DynamicBuffer<T> buffer, int index)
    2.             where T : struct
    3.         {
    4.             var newLength = buffer.Length - 1;
    5.             buffer[index] = buffer[newLength];
    6.             buffer.ResizeUninitialized(newLength);
    7.         }
    That just replaces the element you want to remove with the last element in the array and reduces the length by 1.
     
    Last edited: Jan 10, 2019
    Antypodish likes this.
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,569
    Keeping grown size without shrinking is something convenient in my case.
    But perhaps I misunderstood full concept of mem copy in buffer, when removing multiple elements. I thought it will copy all existing elements, shrinking this way buffer, when possible.

    However, I look again at MemMove in RemoveRange (from screenshot) and my questions is, what is actually moving and where? I see first term is From, then To, and last (third) argument?

     
  10. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,267
    First argument is destination pointer, second argument is source pointer and last argument is size - range of memory which we moving.
    upload_2019-1-10_8-57-17.png
     
    Last edited: Jan 10, 2019
    Antypodish likes this.
  11. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,569
    Thx
    I had first two wrong way rounds then, no to mention last one, which I should deduct. :p
     
  12. jooleanlogic

    jooleanlogic

    Joined:
    Mar 1, 2018
    Posts:
    327
    Oh Eizenhorn beat me to it. :) Yes, it's just moving items from the end of the array down.

    From my reading, when BufferHeader.Pointer is null, it means refer to buffer array in chunk which starts directly after the header. I.e.
    [BufferHeader][value, value, ...][BufferHeader][value, value, ...]
    Only after resizing when memory is allocated on heap does Pointer actually contain an address.
     
    Antypodish likes this.
  13. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,267
    I added image :)
     
    Antypodish likes this.
  14. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    4,569
    Yeah, that clarifies everything now.
    I think for me, a special care person, I need dedicated pictures, to understand anything :D
    Cheers.
     
  15. eizenhorn

    eizenhorn

    Joined:
    Oct 17, 2016
    Posts:
    1,267
    Yeah my bad, i miss this row :)
    upload_2019-1-10_9-53-23.png
    Yeah headers reserve memory for own capacity when initialized
    upload_2019-1-10_9-56-24.png