#First Use Of Jobs Help (Bulk Float Comparisons)

1 messages · Page 1 of 1 (latest)

lunar pelican
#

Hello, I have a poorly performaning piece of code that I'd love to use as an excuse to get into Jobs. I'm hoping someone doesn't mind giving me a super broad rundown of what I would want for this.

I have a meshified terrain where i've converted all the verts into a list of this

struct Triangle
{
  Vector3[] Verts
  Vector3[] Normals
  int[] Indicies
}

I then have a second struct to be used as kinda like a wrapped additional piece of information which is

struct TriangleRelationshipInfo
{
  Triangle Triangle
  List<Triangle> AdjacentTriangles
}

I generate these TriangleRelationshipInfo's by iterating through the list of triangles twice and seeing if both triangles share 2 identical verts.

Now obviously we are dealing with quite a few triangles here so the amount of comparisons and related stuff end up amounting to roughly 4 seconds of raw processing time to complete. I know there's a couple ways I could optimize this logically but for now I'm just curious how I would go about translating this work into a job. Not looking for someone to give me the code 1:1 but some rough pseudocode and api pointers would be kindly appreciated

#

First Use Of Jobs Help (Bulk Float Comparisons)

#

Also happy to provide any additional information if needed of course 😄

lapis tiger
#

well, first of all, you cannot use managed objects in jobs, so you will have to change your arrays to NativeArray / NativeList and friends

I also recommend using types from Unity.Mathematics such as float3, because they are well optimized when you burst compile your code

[BurstCompile] is optional but strongly recommended; you also need to put this on the containing type, otherwise burst will not find it
https://discussions.unity.com/t/when-where-and-why-to-put-burstcompile-with-mild-under-the-hood-explanation/896228

then you create a job struct to which you pass this information, e.g. like this
(not sure about the organization your data structures but you can figure it out)

[BurstCompile] // optional
struct MyJob : IJob
{
  // whatever you need here

  // inputs
  [ReadOnly] public NativeArray<float3> verts;
  [ReadOnly] public NativeArray<float3> normals;
  [ReadOnly] public NativeArray<float3> indices;

  // outputs
  public NativeList<Triangle> adjacentTriangles;

  public void Execute()
  {
    // work here
  }
}

finally you schedule it, and you should take care of dependencies on destruction and such

JobHandle _deps;
void OnDestroy()
{
  _deps.Complete();
}
void Update()
{
  _deps.Complete(); // completion is fine, but you can also pass it into job.Schedule(..) instead
  MyJob job;
  job.verts = myVerts;
  ...
  _deps = job.Schedule();
}
#

there are other job types too, like IJobFor which is useful for parallel execution on independent items (e.g. if you would want to pass on all triangles independently)

lunar pelican
#

Thank you for the response! If you don't mind a couple questions,

Assuming .Complete() in that snippet stalls main thread until that's complete.

In my scenario without logical optimizations for each Triangle i iterate through all the triangles right, so with that in mind would handling that in a single Job by default disperse that out between multiple threads adequately or would I want to split this into multiple jobs (which is maybe what you mean with the Parallel ones?)

#

Also for clarity of course none of these comparisons right now are reliant on others being complete or anything so i don't think i need any dependency based stuff right

lapis tiger
#

Assuming .Complete() in that snippet stalls main thread until that's complete.
absolutely

#

if you can split the work appropriately you can schedule individual jobs, yes

#

if you just do a brute force n² comparison you could schedule work for the number of triangles and read all your data in parallel, but you have to ensure you write to independent outputs

#

there are various collections that allow parallel writing, each with their own performance and usage constraints

for example, NativeList has a ParallelWriter struct you can use to append to a list in parallel, which simply does an atomic increment to get an independent index to write to, but you need to set the size capacity of this list in advance

#

of course you can write to independent indices of NativeArray without a problem.
at least for IJobFor with NativeArray, it will just work, in other cases you may need to disable the safety system (but then triple check you don't overlap writes of course)

#

Also, you cannot nest NativeXXX containers, so NativeList<NativeList<int>> is not allowed

#

this is due to limitations of the safety system which cannot inject safety handles properly for nested structures

#

you can use NativeList<UnsafeList<int>> in that case, but of course the unsafe list will not have safety checks (it still has bounds checks though)

#

also useful to know: you can pass NativeList and friends by copy without a problem; they are simply pointers to underlying Unsafe collections

UnsafeList and friends contain important data in the structure directly, such as Length, which needs to be modified if you add an element, so be careful with copying those

#

to answer your questions more directly

with that in mind would handling that in a single Job by default disperse that out between multiple threads adequately
by default, no, IJob runs on a single thread
IJobFor has separate Schedule and ScheduleParallel for this

Also for clarity of course none of these comparisons right now are reliant on others being complete or anything so i don't think i need any dependency based stuff right
you will still need to wait for the result to be ready somewhere, so you have to store that dependency and the outputs somewhere for later Complete() and data retrieval

you can combine the dependencies of multiple jobs like this

in serie:

JobHandle dep1 = myJob1.Schedule();
JobHandle dep2 = myJob2.Schedule(dep1); // is run after dep1
JobHandle dep3 = myJob2.Schedule(dep2); // is run after dep2

dep2.Complete(); // will complete the entire dependency chain, so dep1 is also complete now, but not dep3

// here you can use whatever is output from job 1 and 2

dep3.Complete();
// yay 3 is done

in parallel:

JobHandle dep1 = myJob1.Schedule();
JobHandle dep2 = myJob2.Schedule(); // is run in parallel with 1
JobHandle dep3 = myJob2.Schedule(); // is run in parallel with 1 and 2

JobHandle combinedDeps = JobHandle.CombineDependencies(dep1, dep2, dep3);

dep2.Complete(); // completes only #2, the others may still be running

combinedDeps.Complete(); // completes all three
lapis tiger
#

Note that the performance from burst-compiled code can be over 20 times faster than unoptimized C#, so your 4 seconds may shrink significantly already just because of that, even without running it on a thread (i.e. using myJob.Run(), which runs it on the main thread immediately)

lapis tiger