Repository navigation
Start greedy participants with their own chunk, and check failures with one flag - #69
Merged
Merged
Conversation
maleadt
force-pushed
the
tb/greedy-own-chunk
branch
from
October 25, 2026 22:10
813b878 to
c17d96c
Compare
Collaborator
Author
|
AI-generated summary of the rework: Rebased onto the reworked #68, with fixes:
|
This was referenced Oct 26, 2026
…ndex
A greedy region handed out every chunk from the shared counter, so the chunk a core ran
changed from one loop to the next, and back-to-back loops over the same data found little
of it in the cache of the core that ran it. With as many chunks as workers it also paid
the counter's round trips for nothing.
Now participant `k` runs chunk `k` first, placed as in a static region, and the counter
hands out chunks `p + 1:m` only; a loop with no more chunks than workers runs as a static
region. The dispatcher runs the first chunk of a participant whose worker has not started,
as it does for a static region, instead of taking the participant back: that chunk has to
run. Exceptions keep their semantics: the counter stops at the first failure noticed, the
started chunks finish, the lowest failing chunk is rethrown, and every chunk before it has
run (the first chunks always run).
Xeon E3-1245 v6, `-t 4,0` pinned, `run_chunks` loops of equal total work, median ns over
4 to 7 processes (static: 272, 452, 743):
m = P m = 2P m = 4P
empty 404 -> 292 431 -> 416 526 -> 476
store 4356 1308 -> 472 1507 -> 973 1783 -> 1487
axpy 1e4 1407 -> 777 1830 -> 1407 2139 -> 2024
The same holds for `-t 4`, `-t 8,0` and `-t 8`, and on an M1 (`-t 4,0`, m = 2P: empty
646 -> 398, store 1300 -> 1056, axpy 2319 -> 1985). The static path is unchanged.
On the caller, a greedy region ran under three exception handlers besides `dispatch!`'s
(the release of the pool in `run_chunks`, the cleanup of `run_greedy`, the participant's
own), and reset and scanned the participants' exception slots around every region. Now a
failing participant also sets the pool's `failed` flag, which `dispatch!` checks after
every region anyway; only then does it look for the lowest failing chunk, and it releases
the pool in its own handler, as for a static region. The slots are emptied with the rest
of a failed region's state, so they are empty between regions.
Each participant keeps its handler: it records the index of the failing chunk and stops
the counter, which the worker's handler can not do.
Xeon E3-1245 v6, median ns, before -> after:
-t 4,0 m = 2P -t 4,0 m = 4P -t 8,0 m = 2P -t 8,0 m = 4P
empty 416 -> 396 476 -> 446 678 -> 612 916 -> 826
store 4356 973 -> 949 1487 -> 1488 1243 -> 1192 1656 -> 1591
axpy 1e4 1407 -> 1351 2024 -> 1998 1681 -> 1577 2209 -> 2101
The optimized code of a static `run_chunks` is the same as before, up to names.
Since greedy participants start with the chunk of their own index, `run_on_pool` ran a greedy loop with no more chunks than workers as a static region, so every greedy loop body compiled two regions: one for the body and one for its `GreedyJob`. The first greedy loop of a body took about 150 ms longer to compile than before (Ryzen 9950X, Julia 1.12, 4 threads: 555 ms against 405 ms). A greedy loop now always runs as a greedy region. With `m == p` its participants run their first chunk and skip the counter, as cheap as a static region. With more chunks, participants load the counter before they claim, so the claims that find nothing left only read a shared line. `foreach_chunk` keeps its static region for `m == p`: it is compiled once per type.
maleadt
force-pushed
the
tb/greedy-own-chunk
branch
from
October 26, 2026 07:00
c17d96c to
9fc1bb5
Compare
Benchmark Results (Julia v1)Time benchmarks
|
This was referenced Oct 26, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The greedy scheduler is what KernelAbstractions.jl's host kernels need for robustness: on a shared node a busy core stalls a static split for a whole launch (giordano: 38 ms against 14.6 ms per Oceananigans step), and dynamic chunks only help with work nobody has claimed yet. But a greedy loop cost 1.5–2.6× a static one even with as many chunks as threads, and vchuravy noted on KA#906 that it "hasn't seen as much tuning".
p+1:mfrom the counter. Back-to-back loops over the same data find the start of their work in the cache of the core that touched it last, and the counter is only touched for the chunks beyond the firstp. A participant whose worker hasn't started is run by the caller, as in static regions.failedflag, which drops thetry/finallyblocks around greedy regions and the reset and scan of the exception slots around every region. Each participant keeps its own handler, since only it knows which chunk failed and can stop the counter. The semantics don't change: once a failure is seen no more chunks are handed out, the started ones finish, and the lowest failing chunk is rethrown.Against the base, median ns over 5 interleaved rounds, Julia 1.12 (taurus is a Ryzen 9 5950X at
-t 16,0, orin a Jetson Orin at-t 6,0):Greedy loops were faster in every round on both hosts; the static loop is within noise. At 2P chunks, data loops still cost 1.1–3.4× a static loop on the hosts measured, which callers with short balanced loops should keep in mind. With a process spinning on one worker's core, greedy axpy over 1e6 elements at 16 threads goes from 119/164 µs (median/p90) to 51/83 µs. The first greedy loop of a new body compiles about 60 ms longer than on the base (Zen 3), where an earlier version of this PR added about 250 ms.
The static path's code is unchanged. New tests cover the placement of the first chunks, the error semantics, and that an error from one loop isn't reported by the next.
Pkg.test()passes on Julia 1.10, 1.12 and 1.13 with 1 and 4 threads, apart from the 3 Enzyme errors main also has.