Performance#
How long Planning Engine takes depends on the shape of the plan and what you ask it to do. Scheduling a dependency chain, previewing an edit to a small part of a project, and leveling heavily contested resources are different workloads.
The measurements below cover all five platforms: native Rust, WebAssembly (WASM), JavaScript (JS), .NET, and Java. All times are in milliseconds, shown as medians rounded to three decimal places. They come from one set of saved benchmark runs. They aren't latency guarantees for every project or device.
For every benchmark case and the complete set of measurements, see the full performance report.
Scheduling a complete plan#
When you open a project or rebuild its schedule after a major change, the engine has to place the whole plan. The work might be one long delivery chain, lots of parallel activities, or separate groups of linked tasks.
| Workload | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| Chain: 1,000 tasks, 999 links | 1.744 | 2.608 | 4.209 | 1.968 | 4.191 |
| Chain: 10,000 tasks, 9,999 links | 13.609 | 22.266 | 23.493 | 17.638 | 15.375 |
| Wide dependency graph: 1,000 tasks, 999 links | 1.523 | 2.561 | 2.908 | 1.946 | 3.460 |
| Wide dependency graph: 5,000 tasks, 4,999 links | 6.541 | 12.278 | 12.646 | 8.942 | 10.540 |
| Independent chains: 1,000 tasks, 900 links | 1.395 | 2.377 | 2.735 | 1.816 | 2.699 |
| Independent chains: 5,000 tasks, 4,500 links | 6.493 | 10.652 | 11.629 | 8.875 | 8.616 |
Growing the chain from 1,000 to 10,000 tasks raises scheduling time from 1.7 to 13.6 ms in Rust and from 2.6 to 22.3 ms in WASM. On these workloads, time grows roughly in proportion to size.
Backward scheduling#
Backward scheduling works back from a deadline to the latest dates each task can take. In these runs, the deadline falls before every stored start, so every task in the chain has to move.
| Workload | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| Backward chain: 1,000 tasks | 1.290 | 2.294 | 2.433 | 1.844 | 1.846 |
| Backward chain: 5,000 tasks | 6.887 | 11.112 | 11.357 | 8.764 | 7.511 |
| Backward chain with calendars: 1,000 tasks | 1.464 | 2.317 | 2.476 | 2.236 | 2.249 |
| Backward chain with calendars: 5,000 tasks | 7.941 | 11.847 | 11.825 | 9.253 | 9.065 |
Planning backward costs about the same as planning forward over a chain of the same size.
Summaries and hierarchy#
A dependency between two phases can be cheap or expensive depending on how the engine schedules the group. Each workload below links one 1,000-task summary to another with a single relationship.
| Workload | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| Summary link, group moves as a block | 1.779 | 2.982 | 3.746 | 2.611 | 3.711 |
| Summary link, expanded to the children | 29.985 | 35.379 | 139.817 | 49.383 | 90.825 |
| Summary link, expanded and compressed | 36.715 | 45.226 | 157.837 | 53.347 | 71.983 |
| Deep hierarchy: 1,000 tasks under 1,000 nested summaries | 27.803 | 29.858 | 60.589 | 34.139 | 56.565 |
With preservation and group movement enabled, the successor phase shifts as one block, and the whole case takes about 1.8 ms in Rust. With group movement disabled, or when you compress, the one summary relationship expands into a relationship between every pair of children, a million in this case. Then the same 2,002 tasks take 30 to 37 ms in Rust, more than the 10,000-task chain. So dependency structure matters as much as the visible task count.
Working-time calendars#
These plans have 2,000 independent tasks, each 1,000 working units long. Only the duration unit changes.
| Workload | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| Durations in working days | 14.736 | 32.542 | 118.271 | 56.960 | 69.778 |
| Durations in working hours | 19.814 | 17.721 | 13.456 | 43.112 | 23.496 |
With no dependencies to propagate, the time goes into walking each task's working time. How fast a platform does that depends on the unit: JS is the slowest platform on day durations and the fastest on hour durations.
Rejecting a cycle#
When dependencies form a loop, scheduling aborts without moving dates and reports the tasks and links involved. These chains start with a two-task loop.
| Workload | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| Chain with a cycle: 1,000 tasks | 1.441 | 1.932 | 4.713 | 2.820 | 3.540 |
| Chain with a cycle: 5,000 tasks | 20.906 | 24.911 | 76.686 | 42.710 | 74.357 |
Editing an existing plan#
When a user changes a task's duration or previews a drag, the app needs the consequences fast. In these runs, the edit affects a 10-task dependency chain inside a larger project. The rest of the tasks aren't connected to that chain.
| Project size | Operation | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|---|
| 1,000 tasks | Apply edit + solve | 0.071 | 0.114 | 0.377 | 0.124 | 0.290 |
| 1,000 tasks | Apply edit + scoped solve | 0.050 | - | 0.299 | 0.092 | 0.328 |
| 1,000 tasks | Preview edit without committing | 0.131 | 0.179 | 0.580 | 0.214 | 0.375 |
| 1,000 tasks | Send the whole edited plan to schedule |
- | 0.694 | - | - | - |
| 10,000 tasks | Apply edit + solve | 0.695 | 1.062 | 2.725 | 1.379 | 2.434 |
| 10,000 tasks | Apply edit + scoped solve | 0.499 | - | 2.755 | 1.115 | 2.138 |
| 10,000 tasks | Preview edit without committing | 1.298 | 1.701 | 4.292 | 2.608 | 2.990 |
| 10,000 tasks | Send the whole edited plan to schedule |
- | 6.957 | - | - | - |
At 10,000 tasks, applying and scheduling the edit takes about 0.7 ms in Rust and 1.1 ms in WASM. An isolated preview takes about 1.3 and 1.7 ms.
Keeping the project loaded makes these local interactions much cheaper than scheduling it again. In WASM, sending the whole edited plan through the stateless schedule call takes 7.0 ms at 10,000 tasks, about 6.5× the cost of applying the edit to a loaded project. Restricting the scheduling scope cuts the Rust edit time by another 28% here, and the .NET time by 19%.
Resource leveling#
Say a team has more work scheduled at once than its people can handle. Leveling moves or pauses tasks to remove the overload, then updates dependent work. Independent jobs are easier to rearrange than linked delivery stages, especially when people have different working calendars.
| Workload | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| 1,000 tasks, 32 resources; no dependencies | 3.865 | 6.039 | 11.060 | 8.175 | 12.834 |
| 10,000 tasks, 32 resources; no dependencies | 38.526 | 50.662 | 92.310 | 97.243 | 108.462 |
| 5,000 tasks, 9,990 links, 7,500 assignments | 52.654 | 72.772 | 108.428 | 132.156 | 150.289 |
| Same linked batches with differing resource calendars | 80.010 | 103.884 | 276.306 | 193.020 | 223.692 |
With independent jobs and the same modest overload pattern, ten times more tasks raises time from 3.9 to 38.5 ms in Rust and from 6.0 to 50.7 ms in WASM.
The linked 5,000-task plan is slower than the independent 10,000-task plan. Moving work there also affects successors and shared assignments. Differing resource calendars push the Rust time from 52.7 to 80.0 ms, about 52% more, and the WASM time from 72.8 to 103.9 ms. All these plans finish leveling with no overload windows left, with the same number of delayed tasks on every platform.
Comparing alternative assignments#
A planner wants to relieve an overloaded person and compare where their work could go. The engine can evaluate several assignment alternatives against the same plan and show how each one affects finish dates and overload before anything is accepted. Here it compares nine alternatives across a team of 32 resources. Each alternative is either scheduled on its own or also leveled to account for contention.
| Project and evaluation mode | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| 1,000 tasks; schedule candidates | 7.946 | 9.019 | 12.524 | 11.753 | 16.981 |
| 1,000 tasks; schedule and level candidates | 25.760 | 30.103 | 55.867 | 52.463 | 59.476 |
| 5,000 tasks; schedule candidates | 38.663 | 43.973 | 60.070 | 62.779 | 62.765 |
| 5,000 tasks; schedule and level candidates | 150.356 | 174.178 | 308.337 | 307.804 | 327.437 |
| 5,000 tasks; schedule and level using 9 worker threads | 31.979 | - | - | 149.469 | 61.567 |
Scheduling nine alternatives for 5,000 tasks takes about 38.7 ms in Rust and 44.0 ms in WASM. Add leveling and that rises to 150.4 and 174.2 ms, since each alternative also has to resolve its resource contention. These times cover the whole set of alternatives, not a single one. Every platform produces the same candidate scores and the same preferred alternative.
Rust, .NET, and Java can evaluate candidates concurrently using worker threads. With nine workers, the 5,000-task schedule-and-level comparison drops from 150.4 to 32.0 ms in Rust, about 4.7× faster. Java goes from 327.4 to 61.6 ms (5.3×), and .NET from 307.8 to 149.5 ms (2.1×). The candidate scores and preferred result stay the same. That makes more thorough comparisons practical without changing the planning policy. The gain depends on available cores and the workload, and concurrent candidates need more memory. WASM and JS evaluate candidates one at a time.
Choosing a leveling placement strategy#
When a small team is heavily booked across several dependent workstreams, finding a spot for one reassigned task can take many adjustments. This example compares five assignment alternatives across three linked chains: 375 work tasks, three summaries, four resources, and 594 assignments.
The engine offers two placement strategies. Minimal steps reconsiders which task should move as it resolves each overloaded time bucket, which allows finer placement decisions. Skipping contended regions moves work past a longer busy stretch all at once. It's faster here, but it gives up intermediate choices and can produce a slightly worse placement for an individual alternative.
| Leveling placement strategy | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| Minimal steps: finer placement decisions | 327.702 | 361.419 | 460.224 | 569.214 | 679.285 |
| Skip contended regions: fewer intermediate decisions | 139.880 | 157.591 | 205.242 | 245.128 | 281.189 |
Skipping cuts the Rust comparison time from about 328 to 140 ms, a 2.3× speedup. The other platforms gain 2.2× to 2.4×. Unlike worker-based parallel evaluation, though, it changes the resulting plans and can change which assignment is preferred.
The quality tradeoff isn't uniform. In these saved results, two alternatives get slightly worse with skipping: their scores rise from 103 to 105 (lower is better). The other three improve, and the best available score drops from 101 to 86. So the faster strategy gives different placement quality. That doesn't mean the slower strategy always produces the better plan.
Suggesting assignments#
Assignment suggestions fill open slots with eligible people and score each choice by its effect on the plan. Here the engine fills 20 empty slots, scoring 5 candidates for each, on the same 32-resource projects used for comparing alternatives. Suggestions schedule each candidate without leveling.
| Project size | Rust | WASM | JS | .NET | Java |
|---|---|---|---|---|---|
| 1,000 tasks | 162.434 | 190.388 | 163.887 | 186.736 | 157.197 |
| 5,000 tasks | 810.703 | 936.562 | 930.772 | 1,066.325 | 885.686 |
Filling all 20 slots scores 100 candidates in total, so at 5,000 tasks each candidate costs about 8 ms in Rust. The five platforms stay close to one another on this workload. The 5,000-task rows were measured only once or twice each.
Measurement environment#
| Rust | WASM | JS | .NET | Java | |
|---|---|---|---|---|---|
| Report | baseline.json |
baseline-wasm.json |
baseline-js.json |
baseline-dotnet.json |
baseline-java.json |
| Runtime | Rust 1.97.1, release build | Bun 1.2.21 hosting WASM | Node.js 24.7.0 | .NET 10.0.9 | Java 27, HotSpot 64-bit server VM |
| Recorded revision | 152976742631 |
152976742631 |
7f8854929523 |
430b9542e3a0 |
2c9d6f6970ec |
| Warmup runs | 1 | 1 | 1 | 1 | 20 |
All five reports were saved on macOS, ARM64, and each workload takes the median of 5 measured repetitions.
Treat the columns as reference measurements, not a controlled comparison of platforms.
Full-plan timings include input handling, scheduling, and result production. Leveling and evaluation timings include initial loading and scheduling. Edit timings start from a project that's already solved. Startup, WASM initialization, and UI rendering aren't included. Rust, JS, .NET, and Java time one complete JSON API call, from decoding the input to encoding the response. WASM times the calls a JavaScript caller makes: for full plans, serializing the input, the schedule call, and parsing its result; for leveling and evaluation, creating the project, solving, and the leveling or evaluation call itself. Cloning and final disposal fall outside its leveling intervals. A dash means the platform has no measurement for that case.