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.