Repository navigation
Conversation
With `--jobs N`, rayon's default splitter divides the harness slice only about log2(N) times before folding the rest of a chunk sequentially on one worker, and once a worker has started a chunk no other worker can take items from it. With three workers the first job is divided twice (501 harnesses -> 250 -> 125) and the 125-harness chunk runs on a single thread, which is the block Thread 0 ran alone at the end of the run measured in model-checking#4961. Harness run times span milliseconds to the full `--harness-timeout`, so that chunk routinely becomes the tail of the run while the other workers sit idle. Add `.with_max_len(1)` so that each harness is its own rayon job and an idle worker can always take the next one. Results, their order, and the `--fail-fast` abort latch are unchanged; only the order in which harnesses start changes. Add a unit test that runs twelve units on a three-thread pool, where the default splitter queues unit 1 behind unit 0 on the same worker, and checks that unit 1 starts while unit 0 is still running. The test fails without the change and passes with it. Resolves model-checking#4961
There was a problem hiding this comment.
🟢 Approval recommended
The focused scheduling change preserves result ordering and abort handling, with no blocking issues identified.
No open issues 🎉
What changed in this PR
Improves parallel harness scheduling so idle workers can take remaining harnesses instead of waiting on a worker’s sequential chunk, addressing #4961.
Changes:
- Adds
.with_max_len(1)to schedule each harness separately. - Adds a regression test checking that a slow harness does not block subsequent work.
| File | Description |
|---|---|
| kani-driver/src/harness_runner.rs | Adjusts scheduling granularity and adds a concurrency regression test. |
Review effort: Balanced
feliperodri
left a comment
There was a problem hiding this comment.
LGTM, thank you for solving this!
|
Thanks for the review, @feliperodri. I checked the three remaining macOS 14 failures: each stops during dependency setup at The PR is already up to date with |
With
--jobs N, the end of a long run degrades to a single worker: one thread finishes the last few dozen harnesses alone while the others sit idle.run_until_abortruns harnesses withitems.par_iter().enumerate().try_for_each(..). Rayon's default splitter starts withsplits = num_threadsand halves it on every unstolen split, so with three workers the first job is divided only twice (501 harnesses → 250 → 125) before the remaining chunk is folded sequentially on one worker; once a worker has started folding a chunk, no other worker can take items from it. That is the fixed block of 125 harnesses Thread 0 ran in the measurement in #4961. Stolen jobs are re-split finely, so the other two workers balance between themselves, but whoever holds the sequential chunk is stuck with it, and with harness run times spanning milliseconds to the full--harness-timeoutthat chunk routinely ends up being the tail of the run.This change adds
.with_max_len(1)so that each harness is its own rayon job and an idle worker can always take the next one. Results, their order, and the--fail-fastabort latch are unchanged; only the order in which harnesses start changes, and each harness's output is already reported in its own block. The per-job overhead is onejoinper harness, which is negligible next to acbmcinvocation.A unit test runs twelve units on a three-thread pool, where the default splitter would queue unit 1 behind unit 0 on the same worker, and checks that unit 1 starts while unit 0 is still running. The test fails on
mainand passes with this change.Resolves #4961
Manual testing. I could not run CBMC on this machine, so I did not reproduce the
kraken-coremeasurement end to end. To confirm the mechanism I ran a standalone simulation with the same rayon version (1.12.0): 501 sleep-based items on a three-thread pool, with durations correlated by position (blocks of 20–60 consecutive items sharing a scale between 1 ms and 128 ms, as harnesses sorted by source location would be), five seeds, measuring the wall time and the time during which only one worker was still busy:with_max_len(1)Seed 3 shows the reported shape: one worker holds a 125-item sequential chunk and spends 3.3 s of a 9.0 s run alone, and the change removes the tail. The other seeds show that when the chunk happens to be cheap there is no cost to the change.
cargo test -p kani-driverpasses.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.