DOTS//CORE local · not synced

m04 · Jobs · drill · 50 min

Schedule a five-stage pipeline that never stalls the main thread

Why this is a drill and not a lab

The parallel lab measured speedup for one job and showed you the over-completion penalty. One job is the easy case: schedule it, complete it, read the number.

A frame is not one job. It is a graph, and the skill this module actually requires is building the graph so the main thread never waits and the cores never idle — which is a scheduling design problem, not an API question. Nobody can walk you through it because the right answer depends on a dependency structure you invent.

What to build

Five stages, per frame, over the same entity set. Something like:

  1. Integrate — advance positions from velocities.
  2. Spatial bin — assign each entity to a grid cell from its new position.
  3. Neighbour count — for each entity, count nearby entities using the bins.
  4. Steer — adjust velocity from neighbour counts.
  5. Clamp — enforce speed limits and world bounds.

The exact simulation does not matter. What matters is the shape: some stages genuinely depend on earlier ones, and some do not. Stage 5 probably does not need to wait for stage 3. Working out which arrows are real and which you assumed is most of the exercise.

Use enough entities that the work is real — tens of thousands, not hundreds. A pipeline over 500 entities finishes before the scheduler has anything to schedule, and every measurement will be noise.

The conflicts, named

| Constraint | The shortcut it closes | |---|---| | Exactly one Complete() | Completing between stages, which serialises the frame and is the exact over-completion penalty the lab measured | | Genuine data dependencies | Five independent jobs, which is trivially parallel and tests nothing | | Truthful access declaration | Escape-hatch attributes that silence the safety system without earning it | | Two stages provably concurrent | Assuming concurrency because you did not declare a dependency | | No stage does less work | Winning by shrinking the problem |

The first constraint is the whole drill. Every instinct will push you toward a Complete() — to read a count, to check a value, to be sure. Each one you resist is a stall you did not ship.

How to work it

Milestones, not steps.

  1. Draw the graph. Five nodes, arrows for real dependencies. For every pair with no arrow, write one sentence justifying why they cannot conflict. Those sentences are claims you will test.
  2. Build it serially first — schedule, complete, schedule, complete. Measure. This is your baseline and your correctness reference.
  3. Chain the handles so each stage takes only the dependencies it needs. Measure again. The delta is what the graph bought.
  4. Prove the concurrency. Open the Profiler and find your two overlapping stages. If they are not overlapping, your graph and your code disagree.
  5. Test that a dependency is load-bearing. Remove one handle between two genuinely conflicting stages and confirm the safety system rejects it. If it does not, that dependency may have been unnecessary — a finding either way.
  6. Predict, then measure, one [ReadOnly] change. Write the prediction first.

When you are done

Answer without checking your code:

  • Which pairs of stages run concurrently in your pipeline, and what makes them non-conflicting? Is that a property of the data or an accident of your current implementation?
  • Which dependency, if removed, would produce a safety error? Which would produce silently wrong results instead, and why is that pair more dangerous?
  • What was your serial-baseline-to-final ratio, and how does it compare to the core count on your machine? If it is far below, where did the parallelism go?
  • Where did you most want to insert a Complete(), and what did you do instead?
  • If a sixth stage arrived that needed the output of stage 2 and stage 5, where would it go, and would it reduce your concurrency?

That last question is the useful one. A pipeline that is optimal for exactly five stages and degrades badly on the sixth is a structure you will fight.

If you find that only two of your five stages can genuinely run concurrently, that is a legitimate outcome — the dependency structure of a simulation is what it is. The finding worth recording is which dependency is the bottleneck and whether the algorithm could be restructured to break it.

m04.l08