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.

Question Is there a point to using burst compiler for a single job?

Discussion in 'Burst' started by dbonham, Oct 16, 2022.

  1. dbonham

    dbonham

    Joined:
    Dec 13, 2013
    Posts:
    2
    I'm just dipping my toes into DOTS so apologies if I'm completely out to sea here.

    I've implemented an algorithm called priority flood (https://rbarnes.org/sci/2014_depressions.pdf) that fills depressions in a generated terrain to ensure that every point drains to the sea.

    It runs in 4 seconds on a 512x512 grid and generates a ton of garbage. I'm sure this code could be optimized and I'm not sure if the priority queue I'm using is working that well, but I figured if I could implement it in DOTS it might help and serve as a learning experience.

    My question is: since this algorithm starts with a coastline and fills inland, meaning a point cannot be marked as a depression until the entire frontier has been evaluated, I can't see a way to break this up into multiple jobs. Does the "performance by default" method of writing code also speed up an expensive algorithm run a single time? Or does it only provide a benefit if you're running an algorithm hundreds and thousands of times?

    non DOTS code below:

    Code (CSharp):
    1. public class PriorityFloodSingleThread
    2. {
    3.     int[] edge;
    4.     int[] dem;
    5.     Dictionary<int, bool> closed;
    6.     Dictionary<int, float> heights;
    7.  
    8.  
    9.     SimplePriorityQueue<int> open = new SimplePriorityQueue<int>();
    10.     Queue<int> pit = new Queue<int>();
    11.     World.MapGen map;
    12.  
    13.     public PriorityFloodSingleThread(int[] dem, int[] edge) {
    14.         this.edge = edge;
    15.         this.dem = dem;
    16.         map = World.MapGen.Instance;
    17.  
    18.         closed = new Dictionary<int, bool>(dem.Length);
    19.         heights = new Dictionary<int, float>(dem.Length);
    20.  
    21.         foreach (var index in dem) {
    22.             closed[index] = false;
    23.         }
    24.  
    25.         foreach (var index in dem) {
    26.             heights[index] = map.Height(index);
    27.         }
    28.     }
    29.  
    30.     public Dictionary<int,float> Flood() {
    31.  
    32.         foreach (var c in edge) {
    33.             open.Enqueue(c, map.Height(c));
    34.             closed[c] = true;
    35.         }
    36.  
    37.         float pitTop = -1f;
    38.  
    39.         while (open.Count > 0 || pit.Count > 0) {
    40.             int c;
    41.            
    42.             if((pit.Count > 0 && open.Count > 0) && pit.Peek() == open.First) {
    43.                 pit.Dequeue();
    44.                 c = open.Dequeue();
    45.                 pitTop = -1f;
    46.             }
    47.             else if(pit.Count > 0) {
    48.                 c = pit.Dequeue();
    49.                 if(pitTop == -1f) {
    50.                     pitTop = map.Height(c);
    51.                 }
    52.             }
    53.             else {
    54.                 c = open.Dequeue();
    55.                 pitTop = -1f;
    56.             }
    57.  
    58.             foreach (var n in map.GetNeighbors(c)) {
    59.                 if (map.waterMap[n]) {
    60.                     closed[n] = true;
    61.                 }
    62.  
    63.                 if (closed[n]) continue;
    64.                 closed[n] = true;
    65.  
    66.                 if (heights[n] <= heights[c]) {
    67.                     heights[n] = heights[c] + .0001f;
    68.                     pit.Enqueue(n);
    69.                 }
    70.                 else {
    71.                     open.Enqueue(n, heights[n]);
    72.                 }
    73.             }
    74.         }
    75.  
    76.         return heights;
    77.     }
    78. }
     
  2. vectorized-runner

    vectorized-runner

    Joined:
    Jan 22, 2018
    Posts:
    330
    Yes, using Dots will improve the performance of your code significantly even if you don't use multiple cores (IJobParallelFor). Burst will optimize your code and generate better machine code instructions, and Job system will run the code on a Worker thread (even if it's single threaded, your code doesn't need to block the main thread)
     
    xVergilx and dbonham like this.
  3. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,112
    To get best of burst, you need convert managed data types to unmanaged.
    For example in your case Dictionary could turn into NativeHasMap.
    Classes conversion to struts.
    And generally switching mind from OOP to DOD paradigm.

    Burst can gain you even 10x performance gain, if code is burstable.

    Flood algorithms may be difficult to be put on multithreading. Depending on logic. But bursting it, will definatelly gives you massive gain.

    Also, as mentioned above, you could schedule single threaded job, which would allow your calculation to be computer off main thread, meaning on another thread. While still single threaded.
     
    Last edited: Oct 19, 2022
    xVergilx and dbonham like this.
  4. Trindenberg

    Trindenberg

    Joined:
    Dec 3, 2017
    Posts:
    355
    You can run a single threaded job for smaller items (it's quicker to setup) and the multi-thread for longer running jobs.

    As for linking things together on multi-thread rather than completing on run (and maybe wasting precious time), you can add the handle as a dependency when scheduling the new code. Then use Complete at the end to await completion.

    It's definitely blow your mind fast (as if using C++ code).

    List of types here, including NativeQueue

    Namespace Unity.Collections | Collections | 1.3.1
     
    dbonham likes this.