Search Unity

How to cycle trough enormous amount of entities

Discussion in 'Entity Component System' started by Klusimo, Nov 10, 2020.

  1. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Hello here, I have a script that generates entities around the player and then destroyes them if they are far away. More behaviours are on the way, but I cant use normal Entities.ForEach since i am working with several hundred thousands entities and that is per player and i am cycling trough them each frame!

    As you could easily guess, using foreach... caused... certain effects that could be described as bellow freezing temperatures. So foreach is definitely off the list. I can lower the amount of entities, but its still too much.

    So I am asking what should I use to cycle trought all these entities without freezing the device. Any other methods of increasing performance would also be appreciated. Many thanks!
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    It is not only amount important, but also what you do when cycling through inside a job.
    Do you have burst enabled?
     
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    If you have a performance problem:
    1) Show code
    2) Show a Timeline Profiler capture of a problematic frame with both the main thread and job worker threads fully expanded.

    Entities.ForEach should be the fastest solution for this kind of problem unless you have many players. If your data is too big you might need some LOD mechanism, but that should be a last resort.
     
    Klusimo likes this.
  4. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Well my code is actually pretty big and complicated, but if youll want it I´ll post it here. I had just one single behaviour that froze everything: Check if the entity is in loaded part of map, if not, destroy it. I also use jobs to nearly everything and also using burst. It was just stupid of me to think i could cycle trough 640 000 entities each frame. Anyway heres the code I am using right now:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Collections;
    5. using Unity.Mathematics;
    6. using Unity.Entities;
    7. using Unity.Transforms;
    8. using Unity.Jobs;
    9. using Unity.Burst;
    10. using static WorldUtils;
    11.  
    12.  
    13. public class WorldMaster : ComponentSystem
    14. {
    15.     public int ChunkSize = 10;
    16.     public int TileSize = 4;
    17.  
    18.     EntityCommandBuffer ECB;
    19.     EntityArchetype TileArchetype;
    20.  
    21.     NativeList<int2> ActiveChunks;
    22.     NativeList<int2> LoadedChunks;
    23.     NativeList<int2> PendingChunks;
    24.  
    25.     JobHandle Dependency;
    26.  
    27.     protected override void OnCreate()
    28.     {
    29.         TileArchetype = World.DefaultGameObjectInjectionWorld.EntityManager.CreateArchetype
    30.                  (
    31.                  typeof(Translation),
    32.                  typeof(Unloadable)
    33.                  );
    34.  
    35.         ActiveChunks = new NativeList<int2>(0, Allocator.Persistent);
    36.         LoadedChunks = new NativeList<int2>(0, Allocator.Persistent);
    37.         PendingChunks = new NativeList<int2>(0, Allocator.Persistent);
    38.     }
    39.  
    40.     protected override void OnDestroy()
    41.     {
    42.         ActiveChunks.Dispose();
    43.         LoadedChunks.Dispose();
    44.         PendingChunks.Dispose();
    45.     }
    46.  
    47.     protected override void OnUpdate()
    48.     {
    49.         ECB = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>().CreateCommandBuffer();
    50.  
    51.         Entities.ForEach((ref Translation translation, ref Loader loader) =>
    52.         {
    53.             float2 Position = new float2(translation.Value.x, translation.Value.y);
    54.             int LoadDistance = loader.LoadDistance;
    55.  
    56.             Dependency = ActivateChunks(Position, ChunkSize, LoadDistance, ActiveChunks);
    57.             Dependency = LoadChunks(ActiveChunks, LoadedChunks, PendingChunks, Dependency);
    58.             Dependency = GenerateChunks(ECB, TileArchetype, PendingChunks, ChunkSize, TileSize, Dependency);
    59.  
    60.             Dependency.Complete();
    61.         });
    62.        
    63.         Entities.ForEach((Entity entity,ref Translation translation, ref Unloadable unloadable) =>
    64.         {
    65.             float2 Position = new float2(translation.Value.x, translation.Value.y);
    66.             NativeArray<Entity> Entities = new NativeArray<Entity>(1, Allocator.TempJob);
    67.             Entities[0] = entity;
    68.  
    69.             Dependency = UnloadEntity(Position, ChunkSize, LoadedChunks, Entities, ECB, Dependency);
    70.             Dependency.Complete();
    71.         });
    72.        
    73.     }
    74.  
    75.     public JobHandle ActivateChunks(float2 Position, int ChunkSize, int LoadDistance, NativeList<int2> ActiveChunks, JobHandle Dependency = default(JobHandle))
    76.     {
    77.         ActivateChunksJob job = new ActivateChunksJob();
    78.         job.Position = Position;
    79.         job.ChunkSize = ChunkSize;
    80.         job.LoadDistance = LoadDistance;
    81.         job.ActiveChunks = ActiveChunks;
    82.         return job.Schedule(Dependency);
    83.     }
    84.  
    85.     public JobHandle LoadChunks(NativeList<int2> ActiveChunks, NativeList<int2> LoadedChunks, NativeList<int2> PendingChunks, JobHandle Dependency = default(JobHandle))
    86.     {
    87.         LoadChunksJob job = new LoadChunksJob();
    88.         job.ActiveChunks = ActiveChunks;
    89.         job.LoadedChunks = LoadedChunks;
    90.         job.PendingChunks = PendingChunks;
    91.         return job.Schedule(Dependency);
    92.     }
    93.  
    94.     public JobHandle GenerateChunks(EntityCommandBuffer ECB, EntityArchetype TileArchetype, NativeList<int2> PendingChunks, int ChunkSize, int TileSize, JobHandle Dependency = default(JobHandle))
    95.     {
    96.         GenerateJob job = new GenerateJob();
    97.         job.ECB = ECB;
    98.         job.TileArchetype = TileArchetype;
    99.         job.PendingChunks = PendingChunks;
    100.         job.ChunkSize = ChunkSize;
    101.         job.TileSize = TileSize;
    102.         return job.Schedule(Dependency);
    103.     }
    104.  
    105.     public JobHandle UnloadEntity(float2 Position, int ChunkSize, NativeList<int2> LoadedChunks, NativeArray<Entity> Entities, EntityCommandBuffer ECB, JobHandle Dependency = default(JobHandle))
    106.     {
    107.         UnloadJob job = new UnloadJob();
    108.         job.Position = Position;
    109.         job.ChunkSize = ChunkSize;
    110.         job.LoadedChunks = LoadedChunks;
    111.         job.Entities = Entities;
    112.         job.ECB = ECB;
    113.         return job.Schedule(Dependency);
    114.     }
    115.  
    116. }
    117.  
    118. [BurstCompile]
    119. public struct ActivateChunksJob : IJob
    120. {
    121.     public float2 Position;
    122.     public int ChunkSize;
    123.     public int LoadDistance;
    124.     public NativeList<int2> ActiveChunks;
    125.  
    126.     public void Execute()
    127.     {
    128.         ActiveChunks.Clear();
    129.         for (int x = 0; x < LoadDistance; x++)
    130.         {
    131.             for (int y = 0; y < LoadDistance; y++)
    132.             {
    133.                 int2 chunk = GetChunkFromWorld(Position, ChunkSize, new float2(0, 0)) - LoadDistance / 2 + new int2(x, y);
    134.                 if (!ActiveChunks.Contains(chunk))
    135.                 {
    136.                     ActiveChunks.Add(chunk);
    137.                 }
    138.             }
    139.         }
    140.     }
    141. }
    142.  
    143. [BurstCompile]
    144. public struct LoadChunksJob : IJob
    145. {
    146.     public NativeList<int2> ActiveChunks;
    147.     public NativeList<int2> LoadedChunks;
    148.     public NativeList<int2> PendingChunks;
    149.  
    150.     public void Execute()
    151.     {
    152.         for (int i = 0; i < ActiveChunks.Length; i++)
    153.         {
    154.             int2 chunk = ActiveChunks[i];
    155.             if (!LoadedChunks.Contains(chunk))
    156.             {
    157.                 LoadedChunks.Add(chunk);
    158.                 PendingChunks.Add(chunk);
    159.             }
    160.         }
    161.         for (int i = LoadedChunks.Length - 1; i != -1; i--)
    162.         {
    163.             int2 chunk = LoadedChunks[i];
    164.             if (!ActiveChunks.Contains(chunk))
    165.             {
    166.                 LoadedChunks.RemoveAt(LoadedChunks.IndexOf(chunk));
    167.             }
    168.  
    169.         }
    170.     }
    171. }
    172.  
    173. [BurstCompile]
    174. public struct GenerateJob : IJob
    175. {
    176.     public EntityCommandBuffer ECB;
    177.     public EntityArchetype TileArchetype;
    178.     public NativeList<int2> PendingChunks;
    179.     public int ChunkSize;
    180.     public int TileSize;
    181.  
    182.     public void Execute()
    183.     {
    184.         for (int i = PendingChunks.Length - 1; i != -1; i--)
    185.         {
    186.             int2 chunk = PendingChunks[i];
    187.  
    188.             for (int x = 0; x < ChunkSize; x++)
    189.             {
    190.                 for (int y = 0; y < ChunkSize; y++)
    191.                 {
    192.                     Entity entity = ECB.CreateEntity(TileArchetype);
    193.  
    194.                     float2 position = GetWorldFromChunk(chunk, ChunkSize, new float2(0,0)) + GetTileCenter(new int2(x, y), TileSize);
    195.  
    196.                     ECB.SetComponent(entity, new Translation { Value = new float3(position.x, position.y, 0) });
    197.                 }
    198.             }
    199.  
    200.             PendingChunks.RemoveAt(PendingChunks.IndexOf(chunk));
    201.         }
    202.     }
    203. }
    204.  
    205. [BurstCompile]
    206. public struct UnloadJob : IJob
    207. {
    208.     public float2 Position;
    209.     public int ChunkSize;
    210.     public NativeList<int2> LoadedChunks;
    211.     public NativeArray<Entity> Entities;
    212.     public EntityCommandBuffer ECB;
    213.  
    214.     public void Execute()
    215.     {
    216.         int2 Chunk;
    217.         Chunk = GetChunkFromWorld(Position, ChunkSize, new float2(0,0));
    218.  
    219.         if (!LoadedChunks.Contains(Chunk))
    220.         {
    221.             ECB.DestroyEntity(Entities[0]);
    222.         }
    223.     }
    224. }
    That is my main code. I am also using my own library of code (using static WorldUtils), from which i used these functions:

    Code (CSharp):
    1. public static float2 GetTileCenter(int2 TilePosition, float TileSize)
    2.     {
    3.         return new float2((TilePosition.x + (TileSize * 0.5f)), (TilePosition.y + (TileSize * 0.5f)));
    4.     }
    5.  
    6.     public static int2 GetChunkFromWorld(float2 WorldPosition, int ChunkSize, float2 OriginPosition)
    7.     {
    8.         int2 ChunkPosition;
    9.         ChunkPosition.x = (int)(math.floor((WorldPosition.x - OriginPosition.x) / ChunkSize));
    10.         ChunkPosition.y = (int)(math.floor((WorldPosition.y - OriginPosition.y) / ChunkSize));
    11.         return ChunkPosition;
    12.     }
    13.  
    14.     public static float2 GetWorldFromChunk(int2 ChunkPosition, int ChunkSize, float2 OriginPosition)
    15.     {
    16.         float2 WorldPosition;
    17.         WorldPosition.x = (ChunkPosition.x * ChunkSize) + OriginPosition.x;
    18.         WorldPosition.y = (ChunkPosition.y * ChunkSize) + OriginPosition.y;
    19.         return WorldPosition;
    20.     }
    Finally I have this testing code from which i create entity with loader component to start the process:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using Unity.Entities;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6. using UnityEngine;
    7.  
    8. public class Test : MonoBehaviour
    9. {
    10.     public float2 Position;
    11.     EntityManager EntityManager;
    12.  
    13.     private void Start()
    14.     {
    15.         EntityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
    16.     }
    17.  
    18.     private void Update()
    19.     {
    20.         if (Input.GetKeyDown(KeyCode.G))
    21.         {
    22.             Entity entity = EntityManager.CreateEntity(typeof (Translation), typeof (Loader));
    23.             EntityManager.SetComponentData(entity, new Translation { Value = new float3(Position.x, Position.y, 0) });
    24.             EntityManager.SetComponentData(entity, new Loader { LoadDistance = 40 });
    25.         }
    26.     }
    27. }
     
  5. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Also I would capture a screenshot, if my unity didnt completely and utterly freeze. Like, I have to exit it using Ctrl + Alt + Delete. And i dont have bad computer.
     
    Last edited: Nov 11, 2020
  6. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    I go through 911683619 entities for my biggest system (desert flora), however I split it up over frames and also sort by distance using a hierarchical approach like a quadtree so that the system only loops over the relevant parts. At most only about 3000 gets checked every other frame. This helped keep performans up.
     
  7. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    you are scheduling a job per entity. that's very bad.
    you should schedule one job (or one job per step) and loop through the entities inside.

    also, check SystemBase
     
    Klusimo likes this.
  8. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    You mean turning Entities.Foreach into job? I saw that recently and i will research it more.

    I see what you mean, but i have no idea how i would implement it. Any tip to start research?
     
  9. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Nyanpas and Klusimo like this.
  10. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
  11. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    The others have already done a pretty good job pointing out problems, but I will highlight the ones that stick out to me.
    1) Use SystemBase instead of ComponentSystem. ComponentSystem has lots of performance problems. When I said Entities.ForEach should be the fastest, I meant SystemBase's Entities.ForEach.ScheduleParallel.
    2) World is a property of SystemBase. You do not need to get the DefaultGameObjectInjectionWorld.
    3) You should cache EndSimulationEntityCommandBufferSystem in OnCreate().
    4) Is there a reason you aren't making whether a Loader is loaded data associated with the entity? Doing the lookups in the NativeList seems unnecessarily slow.
    5) This one might be controversial, but I find it really confusing when people use their own definition of "chunk" when "chunk" is a term used by Unity's ECS.
    6) You are scheduling a job per entity for unloading. That's really slow.
    7) You are scheduling lots of single-threaded jobs and then completing them almost immediately afterward. There is no point in doing that. Just use Run() instead. However, I think if you rewrote this to use SystemBase and put the load status on the entities, you would end up with two Entities.ForEach.ScheduleParallel (one for marking Loaders as loaded or unloaded and potentially creating new entities, and one for destroying entities to be unloaded).
     
    charleshendry likes this.
  12. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Thanks for the tips. I am already working on making it much much better.

    Also a overall question, what is the fastest (most performant) NativeCollection. I heard its NativeHashSet.
     
  13. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    In what aspect? If there was one container that was objectively faster for every use case, the other containers would be deprecated.
     
  14. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Thats not true, its the same as array/list. Array is faster but list can be increased etc.

    I mean it this way.
     
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    A NativeHashSet is faster at telling you whether an instance exists in the container, whereas a NativeArray is faster at iterating through each element inside. This is because a NativeHashSet may have empty slots and consequently has to check whether or not an element is valid when iterating.

    Every container has strengths and weaknesses. I cannot provide you a recommendation until I know the specific operations you wish to perform, and even then, you may want to profile for yourself as there may be other factors that affect performance.
     
    Nyanpas likes this.
  16. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    I use lists and arrays because it is easy.
     
  17. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Thats is exactly what I wanted to know actually, because I iterate over huge amount of data in container. Now I know that the fastest one to iterate over is NativeHashSet, thanks.
    Lmao
     
  18. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    Actually, that's the opposite of what DreamingImLatios stated. His post stated that NativeHashSet may be slower because it may have empty slots and will need to check this during iteration, whereas NativeList can just iterate sequentially through its elements.

    Generally, when evaluating which data structure you're going to use, you are going to want to compare the "big O notation" (or asymptotic notation) of the operations you plan to use. If you're unfamiliar with "big O notation", its basically a way to categorize how a function performs based on the growth rate of its inputs. Here's a link to a quick summary of it (https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/).

    Unfortunately, the documentation for the Unity Collections package doesn't include the big O notation of operations for its data structures. It would be great if this were added at some point. Without that, I generally assume that the big O notation of a Native collection is the same as it would be for the corresponding standard C# collection. However, this is an assumption on my part, not a guarantee from Unity (at least not that I've seen). If you need to be sure you could dig into the source and perform your own analysis though.
     
    DreamingImLatios likes this.
  19. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    I'll also add to my last post that when deciding on which data structure to use, you cannot just consider the performance characteristics of the operations alone. You'll also need to consider the memory usage characteristics of the data structure. Conveniently, these can also be expressed using big O notation.

    On top of this, there are also considerations like spatial locality which should not be ignored if performance is a critical concern. Due to modern processor architecture, it is much faster to access data sequentially than it is to access it randomly. With sequential access, much of the data you'll access will be loaded into the cache for "free" (without having to go to memory), whereas with random access, you'll end up with a lot more cache misses that require going out to memory to get the data. Acknowledging this fact and writing code to take advantage of this is one of the core concepts behind Data Oriented Design. As a practical matter, what this means is that two data structures that state an O(n) complexity for an operation, but where one is implemented using sequential data access and the other using random data access, will actually have very different performance characteristics.

    Finally, I'll just add, NativeList and NativeHashSet aren't interchangeable. NativeHashSet doesn't allow duplicate values, while NativeList does. Depending on your use case that might rule out using NativeHashSet altogether.
     
  20. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Umm... They stated that NativeHashSet is faster when determining if a value is in them, not slower. Also they are not the only one who told me so.

    Edit #1: Oh sorry, I am stupid, I said I iterate over the values, but I just read them, my bad.

    Edit #2: I heard that when iterating over large amount, NativeHashSets are faster than lists, so it is like this? NativeArray>NativeHashSet>NativeList

    Edit #3: Another question, when I need containers that have large amount of data and I want to iterate (not read) trough them, BUT I also have to enlarge them when needed, what container should I use?
     
    Last edited: Nov 12, 2020
  21. DK_A5B

    DK_A5B

    Joined:
    Jan 21, 2016
    Posts:
    110
    First, let me caveat everything by saying that I haven't gone through the source code on the Unity Collections package, so I'm speaking generally about different types of collections, rather than the specific implementations of these collections by Unity in the Collections package. If you need to squeeze every last ounce of performance out of your code, you'll need to dig into the source and understand the nuances of the implementations for yourself and make your decisions based on that.

    With that aside, I'll try to address your questions as best I can. First, where did you hear that iterating over large data sets with a NativeHashSets would be faster than a List? Do you have a source for this? I can't say that it's definitely wrong (as I said, I haven't checked the source for Unity Collections), but I can't see any reason why iterating over a HashSet (that is, stepping through each value sequentially) would be faster than for a List or an array. When iterating, an array is going to be fastest, because that's literally sequential blocks of data you're reading through. A List would be next, because a List is really just a dynamic array (in its most basic form it's a wrapper around an array that allows you to modify the size of it at runtime). For anything else, it will depend on the implementation. If the data structure is backed by an array, then at best it would be as fast as a List, although it could be slower if there is additional overhead while stepping through the array that backs it (for example, checking for "empty" elements in the backing array, as DreamingImLatios mentioned earlier). Any data structure that depends on operations involving random access will be slowed down by cache-misses.

    Also, as I said before, and I can't stress this enough, you can't use HashSets and Lists interchangeably. HashSets do not allow for duplicate values and they also don't guarantee order. Lists allow duplicate values and guarantee the order of values. They just aren't the same thing. Where HashSets excel is when you're searching them for a value. If you have a collection of unique values that you'll frequently be calling Contains(item) on, then that's a good candidate for using a HashSet.

    With regards to your question about what type of collection to use if you have a large amount of data that you're going to iterate through and need to be able to dynamically expand, the quick and dirty answer is "a List". The longer answer is "it depends" - on your data, on how you're going to use it, and also on what you mean by "large". Some things to ask yourself are:
    • How big is large? How many elements? What size are my elements?
    • Does the order of items in the collection matter?
    • How frequently am I going to insert (or delete) from this collection vs read from it?
    • Where do insertions and deletions occur? Are they always at the end of collection? Can they be in the middle of the collection?
    • Do I only need to read it sequentially? Or do I need to support random access?
    • Am I ever going to search the collection to determine if it contains a value? If so, how often am I going to do this?

    All of these questions will help clarify which data structure is best for your specific scenario.
     
    DreamingImLatios likes this.
  22. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    DOTS/ECS is currently more a loose collection of possibilities than a userfriendly framework that makes it easy to develop games. I don't want to consider any of this when I make my games ("performance by default", remember?), I just want an API that handles everything for me so I can spend my time working on the game mechanics themselves instead of making systems that allow me to make the game mechanics...
     
    Krajca likes this.
  23. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    If by "they" you are including me, that is incorrect. I suspect there may be a language barrier here if you came to that conclusion.

    Iterating can refer to either reading or writing. It doesn't make much of a difference here.

    Who told you this? A list is just a pointer to an array where that pointer can change on resize. Depending on your usage pattern, Burst can sometimes make a NativeList just as fast as a NativeArray. Otherwise it is an indirection cost which usually hits cache. A NativeHashSet can get pretty close when using the iterators, potentially to the point where you can't measure the difference, but there is no way I could ever imagine it being faster than a NativeArray for linear access.

    This depends on how you construct your data. If it is very large linear-write linear-read, I tend to use a custom container that doesn't need to reallocate the entire array on a resize. If you are reading a lot more than writing, NativeList should be sufficient. You can temporarily treat a NativeList as a NativeArray using AsArray or AsDeferredJobArray so that Burst knows not to do the indirection.

    DK_A5B is providing excellent advice that I am in full agreement with. You either listen to both of us, or neither of us.

    Then don't worry about this unless you have performance problems. Very often using the wrong container in Burst still beats Mono (and IL2CPP). While I spend some time investing in optimizations for problems that show up over and over again (mostly because I think it is fun), many times I write code using whatever native container is most convenient and I might even litter my code with a bunch of if/else blocks and scalar operations. Despite that, most of my stuff still looks impressive compared to classical Unity, especially given how little time I have spent. And if you are a maximum performance junkie, then forget the phrase "performance by default" because what that phrase actually means is no longer relevant to your goals, and you will be disappointed for thinking as such.

    I would say DOTS/ECS (the usable parts) is more of a performance-aware framework and architecture rather than an engine right now. If you have never built a game without using an engine, it can be difficult to go in with the right mindset.
     
    Nyanpas likes this.
  24. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    For the most difference of hashMaps is, they do not guarantee an order of accessed and iterated elements. You can access individual elements rapidly however.
    With NativeArray colections is easy to modify read / write in jobs, when iterating and you guarantee the order of placed elements.
    In fact, in some cases you may want having an array of keys, to actually access values of hashMaps in specific order. So you combine different collections together, rather than fight, which one to use.

    With list, if you add and remove elements dynamically, that potentially introduces additional performance reduction. Specially when start dealing with elements, which are not on the back of the list. I for example rather predifine size / capacity of arrays, or buffers when possible and just use index to write to the collection.
     
  25. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Sorry I misunderstood it. Its not that I dont want to listen, sometimes I just dont understand it immediatelly.

    Anyway this person told me about lists being slower but I might have misunderstood it (so dont take this as me pointing a finger and saying "Not my fault! HE SAID IT TO ME!!!" as a bullcrap excuse). Here are links to my past questions on Unity Answers:

    https://answers.unity.com/questions/1787282/how-to-cycle-trough-enormous-amount-of-entities-do.html

    https://answers.unity.com/questions...-jobs.html?childToView=1787497#answer-1787497

    Sorry if that "lmao" sounded rude, I just found it funny, nothing more

    1. Well I am potentially working with thousands - hundreds of thousands of values.
    2. Order does not matter, since I just do .Contain pretty much and work with specific value at a time.
    3. I work with it every frame, both add, read and remove from it.
    4. Every value that is "unnecessary" is removed so it can be anywhere.
    5. I dont quite understand what you mean by sequentially/random access, but if you mean it that I either go trough the list one at a time/randomly I do kind of both(?), at one time I am going trough the entire container one by one, other time I just do .Contains to test if the value is there.
    6. I test for values in container every frame.

    Anyway thanks everyone for info, tips etc. I really appreciate it.
     
  26. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    In such cases, you should use NativeHashmap when possible, if you got thousands+ elements.
    Also, you avoid this way duplicates.
    Or if you want duplicates, use NativeMultiHashmap.

    If you do that in case of large lists, you shift other existing elements to empty location. Like defragmentation. That is potential performance hit, depending how often you do that.

    If I were you, I would suggest run benchmark for your use case, if is worth to use list, or NativeArray for example, with predefined length / capacity. Even tho, you may clear some unused elements. Made them spare. You can always reuse spare spaces later. See how it compares to list for example.
     
  27. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    I hope Unity is paying you.
     
    Klusimo likes this.
  28. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Thanks, I´ll test and see what happens. Also I dont quite understand what you mean by "elements being shifted to empty location".
     
  29. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    Think about of how defragmentation in computer works.
    Fragmented memory is your array, which data is moved, to fill gaps.
    So when you iterate next time, you don't have gaps, after removed elements.
    But instead, size of the list is reduced.

    I.e. removing second element in the list.
    Code (CSharp):
    1. Before Removing
    2. 1
    3. 2
    4. 3
    5. 4
    6. After Removing
    7. 1
    8. 3
    9. 4
    If you don't shift after removal, you would have a gap, between 1 and 3 elements.
     
    Last edited: Nov 13, 2020
  30. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    I see, thanks, also isnt there a function specifically for it?
     
  31. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,778
    What I mean is, when you remove elements from the inner list, other elements will be automatically moved, to fill the gap. You don't need do that manually. However, this introduces additional performance cost for lists containers, while having flexibility. Can be crucial, if you do such operation every frame, on thousands of elements.
     
  32. Klusimo

    Klusimo

    Joined:
    May 7, 2019
    Posts:
    76
    Oh that makes sense. Is there any way to prevent/counter/ease it?
     
  33. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,270
    No worries. Anyways, the Contains method is a "search" method, not an iteration method. NativeHashSet and NativeHashMap are data structures which sacrifice iteration speed and iteration ordering in exchange for fast searching. This is why you want to articulate how you are interacting with the container rather than just (put stuff in and get stuff out). In the answer provided and earlier in this thread, it was suggested that you use NativeHashSet instead of NativeList because you had a Contains call in an inner loop. Such a suggestion is valid in this context (although I personally believe you don't need any containers for your problem other than EntityCommandBuffer).
    Nah. I don't want money. I just make statements based on what I believe to be correct and assume that if I am wrong someone will point out my mistake and teach me something new. And in this particular case, I have pretty high confidence due to this thread of yours: https://forum.unity.com/threads/how-low-level-to-achieve-the-benefits-of-using-dots.972255/
     
    Klusimo likes this.