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. Dismiss Notice

Native counter in parallel jobs: concurrency issues

Discussion in 'Entity Component System' started by creepto, Jun 22, 2022.

  1. creepto

    creepto

    Joined:
    Dec 27, 2021
    Posts:
    18
    I need a unique global counter variable that is incremented inside parallel jobs.
    As pointed out from another thread, in https://docs.unity3d.com/Packages/com.unity.jobs@0.50/manual/custom_job_types.html, there's an example of a Native Counter implementation.
    Those are the ways it can work (correct me if I'm wrong):
    1) Interlocked atomic writings, when each thread locks an access and then writes to the variable (i.e. the cache line). This means a single cache line will be used by N threads and as the article says "this is not optimal as it means the same cache line is used by all threads. The way this is generally solved in NativeContainers is to have a local cache per worker thread, which is stored on its own cache line".
    2) So this leads to the second solution, a shared cache line copied to single-thread local cache lines, then each thread performs an increment on his local cache variable. Original counter is then incremented in the main thread. "Writing the NativeCounter this way significantly reduces the overhead of having multiple threads writing to it. It does, however, come at a price. The cost of getting the count on the main thread has increased significantly since it now needs to check all local caches and sum them up".

    So, as far as I got, solution 2 too is not optimal because it needs to perform N sums on main thread. I'm currently uncertain whether game-state counters implemented on monobehaviors (I'm making a hybrid game) would make a big deal out of less performance, with respect to parallel jobs counter. Let alone I have no idea if I can create a variant of the structure NativeCounter described in the link above (usable just in IJobParallelFor) in ScheduleParallel of a foreach. I'm currently having issues using unsafe pointers with foreach. Any thoughts on that? Should I give up on monobehaviors on this?

    Thank you in advance!
     
  2. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,574
    How many instances in total you may have in a job? 100, 1k, 10k, 100k?
    Depending on that, if it is small count like 1k for example, it may be worth just iterate these on main thread.
    Also, how often you execute this job?
     
  3. DrBoum

    DrBoum

    Joined:
    Apr 19, 2020
    Posts:
    26
    hello,
    if you really want to do it in parallel, you could have a native array of integers that is JobsUtility.MaxJobThreadCount length,
    then you would access your counter using the thread id that you can get on jobs with the attribute [NativeSetThreadIndex] safely as only one thread would write to any index at any given time
     
    creepto likes this.
  4. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    383
    I've done the benchmark before, summing version with gaps is faster than using locks. Also it's not N sums, the amount of sums is constant
     
  5. creepto

    creepto

    Joined:
    Dec 27, 2021
    Posts:
    18
    @Antypodish it's a mobile game, so even 1000 entities would be unthinkable. It's a crowd game and the counter is incremented whenever a new member joins the crowd (how often will depend on crowd velocity, number of NPC, and other parameters level designers will play with). For now, since I have time constraints, I've done it via monobehavior, as in general I have one GameObject referencing each entity:
    Code (CSharp):
    1.  
    2. //late update
    3. if (!_isFollowing)
    4. {
    5.     _isFollowing = _entityManager.GetComponentData<IsFollowingData>(EntityToFollow).Value;
    6.     if (_isFollowing)
    7.         GameManager.main.FollowingAlliesPoints += 1;
    8.  
    9. }
    that's not too bad considering that with branch prediction, this branch would run only once for each character. But I definitely want to transfer every game controls to jobs and their data structures as soon as I don't have too restricted deadlines, keeping gameobjects just for animations.