Skip to content

Optimizations to multi-process and multi-rank Datadeps - #739

Open
jpsamaroo wants to merge 6 commits into
jps/mpi-bad-scale-stencilfrom
jps/hierarchical-mp-mr
Open

Optimizations to multi-process and multi-rank Datadeps#739
jpsamaroo wants to merge 6 commits into
jps/mpi-bad-scale-stencilfrom
jps/hierarchical-mp-mr

Conversation

@jpsamaroo

Copy link
Copy Markdown
Member

Written by Claude Opus

jpsamaroo and others added 6 commits August 15, 2026 03:15
Three costs that each task paid regardless of how much work it did, and
which therefore grew to dominate as chunks got smaller with more workers
or ranks:

* `compatible_processors` scanned every processor in the cluster and
  constrained the scope against each. A scope built from `ExactScope`s
  already names its processors, so `scope_processors` reads them off
  directly and only checks that they still exist and are enabled. This is
  the shape Datadeps hands the scheduler for every pinned task.
* `estimate_task_costs!` walked a task's chunks to rank candidate
  processors even when there was only one candidate to rank. Datadeps
  pins each task to a single space, so this was pure overhead.
* MPI's uniform dispatch ran `promote_op` and `infer_effects` per task
  per rank to decide whether the result needs a status broadcast.
  Uncached, each dispatch allocated megabytes of inference state on
  every rank, and because ranks advance in lock-step the resulting GC
  pauses did not overlap with anything. Both queries now go through
  memos (`cached_return_type`, the new `cached_nothrow`).

Co-authored-by: Cursor <cursoragent@cursor.com>
`slot_is_already_in_place` decides whether a chunk can serve as its own
slot instead of being copied. It required the chunk to be locally owned
and unwrappable here, because it answered the question by unwrapping the
value and asking whether `move_rewrap` would treat it as a leaf. That
excluded exactly the cases where a needless copy is most expensive:
under MPI every rank plans every task but owns only some of the data,
and under Distributed the chunk is often homed on another worker. In
both, the alternative is a `move_rewrap` that reproduces, message by
message, data already sitting where it is needed -- for a wrapper like
`HaloArray`, a header broadcast plus one transfer per child, per
argument, per region.

`slot_rewrap_is_identity` answers the same question from `chunktype`
alone: a rewrap is the identity unless the type contains a handle whose
resolution is the point of the rewrap (`Chunk`, `DTask`, `ChunkView`),
so wrappers over plain data now pass through. `chunktype` is uniform
across ranks, so the decision is too.

Co-authored-by: Cursor <cursoragent@cursor.com>
…anning

Turning a prepared spec into scheduler thunks needs nothing from the
planner, but it was running inline and accounted for ~40% of per-task
planning cost in a multi-worker region. `AsyncEnqueueQueue` hands each
batch to a submitter task instead, preserving FIFO order (the syncdeps
recorded during planning rely on it) and keeping a synchronous drain for
the two points that need a task to really exist: a value dependency's
`fetch`, and the end of the region. It is used only when there is a
spare thread to submit on, and never under uniform execution, where a
rank's submission runs collectives that must stay ordered against
planning's own.

`DATADEPS_BATCH_LIMIT` goes from 4 to 16, which is where the scheduler
round-trip stops amortizing (256 independent `InOut` tasks over 4
workers: 69 us/task unbatched, 55 at 16, 53.5 unbounded) while still
bounding how far planning runs ahead of execution. Together these take
that region from 31.2 ms to 16.9 ms.

Attributing planning cost is hard from a profile, because the expensive
parts are blocking waits inside communication rather than hot loops, so
this also adds per-phase timing behind `JULIA_DAGGER_HIER_TIMING=1`
(off by default, one `Ref` read per phase) and records what it found in
the module header. The MPI hang warning now carries a backtrace, since
which call site is waiting is the whole diagnosis for a wait cycle.

Co-authored-by: Cursor <cursoragent@cursor.com>
A chunk's aliasing info cannot be computed locally: under Distributed it
is a `remotecall_fetch` to the owner, and under MPI a broadcast from the
owner that every rank must join. Planning asks the same questions
repeatedly -- once per unique argument to build the DAG, again for every
slot, again for the write-back epilogue -- so a region spent hundreds of
round-trips re-deriving a handful of distinct answers. Under MPI each is
a global synchronization point, which is what made replicated planning
scale so poorly with rank count.

Three changes, all invisible to the user:

* `ChunkAinfoMemo`, a per-region memo keyed on argument identity, the
  dependency modifier and the acceleration. Per-region because aliasing
  info describes where a value's memory currently is: stable while one
  region plans, but a later region's chunk may reuse a freed address.
  Keys are rank-uniform, so every rank hits and misses on exactly the
  same calls and the remaining broadcasts are still collective.
* `batch_aliasing` / `batch_ainfos`, which resolve a whole uniform list
  of arguments in one exchange per owning rank rather than one broadcast
  each, and seed the memo with the results. Phase 1 now goes through
  these, so the rest of planning finds its answers already computed.
* Copies made by Datadeps recorded their own destination-side ainfo
  eagerly, costing a second rendezvous per slot on top of the transfer.
  That ainfo only matters when the copy is itself the source of a later
  move, which most regions never do, so copies are now recorded
  unresolved and resolved lazily on the first `derived` miss, as one
  batch. Safe under SPMD because the trigger is uniform.

Slot generation halved for a 4-rank stencil sweep (1.97 -> 0.96
ms/sweep), and the 2-rank MPI test suite went from 12m20s to 10m07s.

Co-authored-by: Cursor <cursoragent@cursor.com>
A slot is the per-memory-space buffer Datadeps allocates to stand in for
an argument a task will touch from somewhere other than where the
argument lives. Slots were thrown away at the end of every region, so an
iterative workload -- a stencil loop, an iterative solver -- rebuilt the
same buffers every sweep, paying an allocation, a whole-buffer transfer,
an aliasing exchange and a free task per buffer. Under MPI the transfer
is worse than its bytes suggest: it is a rendezvous every rank has to
reach, in the middle of planning.

Slots are now kept in a bounded cache keyed on (origin chunk identity
hash, destination space) and handed to the next region that wants the
same pair.

The transfer this removes is the part that looks unsafe, so: a fresh
slot's populating transfer is already known to be redundant for the data
the region writes, because `generate_slot!` deliberately does not
synchronize with the owner (a slot is only ever "some version" of the
data) and the region's own copy-to brings it current before any task
reads it. What the transfer does provide is coverage of the parts the
region's copies never refresh. So reuse is restricted to arguments used
only under `dep_mod === identity`, whose first copy is a whole-buffer
copy; an argument touched under any partial modifier is excluded
outright, including its `identity` uses, since they all share one slot.
Establishing that needs the region's whole argument list before any slot
is built, which is why this rides on the hierarchical path's Phase 1 --
the flat path discovers arguments task by task and stays as it was.

Retention is keyed on `Chunk.handle`, not on the `Chunk`: a slot is
retained as the object in `state.remote_args` and freed as the object in
the aliased-object cache, and those are not always the same wrapper over
the one buffer.

Every input to a reuse decision is rank-uniform -- the identity hash,
the chunk type, the eligibility set, and the cache contents, which
evolve identically because every rank replays the same regions in the
same order -- so all ranks hit and miss together, which they must, since
a hit skips a rendezvous. Eviction is FIFO rather than LRU so a hit does
not reorder the eviction sequence.

For a 4-worker, 4x4-block sweep of 256^2 blocks, steady-state planning
goes 110 -> 14 ms and the whole sweep 86 -> 32 ms; the same benchmark on
2 MPI ranks goes 15.4 -> 13.3 ms. `Dagger.DATADEPS_SLOT_REUSE[]` (or
`JULIA_DAGGER_SLOT_REUSE=0`) disables it.

Co-authored-by: Cursor <cursoragent@cursor.com>
`stencil_region_info` extracts each cross-space halo region in its
owner's memory space, so that only the region crosses the boundary
rather than the whole neighboring chunk. It extracted into a freshly
allocated array every sweep, and a new array is a new `Chunk` that
Datadeps has never seen in the reader's space: for a 4x4-block sweep
over 4 workers that is 96 slots per sweep, each needing a full transfer
of the region during planning, on top of the copy the region already
schedules to bring that slot up to date. The cross-region slot cache
could not help, since it keys on the origin chunk and the origin was new
every time.

Those buffers are now kept, keyed on the source chunk and the region
(which corner, how deep, under which boundary condition, since a
boundary region of the same corner holds different values), and
re-extracted into with `halo_region_into!`. The chunk identity stops
changing, so the slot cache recognizes it and keeps the reader-side
buffer too; the two caches compose, and on this workload neither does
anything without the other.

Only the allocation is reused. Unlike a slot, a halo region is a source:
nothing downstream refreshes it, so its contents are re-extracted every
sweep.

A source is not always a `Chunk` -- a `DArray`'s chunk entry is a `DTask`
until something collects it -- and both forms are stable across sweeps,
so the cache recognizes either. The self-writing form
`A[idx] = f(@neighbors(A[idx]))` reads a pre-sweep snapshot instead,
which is a fresh object every sweep, so `stencil_source_chunks` now
reports what each snapshot stands for and the cache keys on the chunk
behind it.

Eviction is FIFO and deliberately does not drop entries whose source
array has been collected, tempting as that is: liveness is rank-local,
so ranks would evict different entries and stop agreeing on which
extractions to spawn. `check_uniform` asserts the hit/miss decision
agrees across ranks.

A buffer stays checked out from when it is taken until the region
reading it has finished, so two `@stencil` blocks running concurrently
over one array cannot have one overwriting what the other reads; the
loser extracts into a fresh array.

Median of 15 sweeps, 4x4 blocks of 256^2: 246 -> 170 ms on 4 Distributed
workers, 306 -> 246 ms on 4 MPI ranks. In the Distributed case slot
generation goes 111 -> 33 ms as all 96 moves become cache hits.
`Dagger.STENCIL_HALO_REUSE[]` (or `JULIA_DAGGER_STENCIL_HALO_REUSE=0`)
disables it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

Dagger benchmarks: dirty vs master

Multi-threaded benchmarks (4 threads)

Dagger benchmarks: dirty vs master

master dirty master / dirty
array/dagger/N=1024 (block 512)/add (X + X) 3.85 ± 0.86 ms 3.63 ± 0.81 ms 1.06 ± 0.33
array/dagger/N=1024 (block 512)/alloc (rand) 3.32 ± 0.088 ms 3.54 ± 0.25 ms 0.938 ± 0.07
array/dagger/N=1024 (block 512)/broadcast (X .+ 1) 2.78 ± 0.45 ms 2.66 ± 0.43 ms 1.04 ± 0.24
array/dagger/N=1024 (block 512)/map (sin.(X)) 7.17 ± 0.98 ms 6.79 ± 0.94 ms 1.06 ± 0.21
array/dagger/N=1024 (block 512)/norm 1.29 ± 0.025 ms 1.62 ± 0.42 ms 0.795 ± 0.2
array/dagger/N=1024 (block 512)/reduce (sum) 2.47 ± 0.4 ms 2.4 ± 0.077 ms 1.03 ± 0.17
array/dagger/N=1024 (block 512)/transpose (permutedims) 7.02 ± 0.29 ms 7.2 ± 0.59 ms 0.974 ± 0.09
array/dagger/N=256 (block 256)/add (X + X) 1.06 ± 0.17 ms 1.22 ± 0.2 ms 0.869 ± 0.2
array/dagger/N=256 (block 256)/alloc (rand) 1.02 ± 0.12 ms 1.07 ± 0.22 ms 0.956 ± 0.22
array/dagger/N=256 (block 256)/broadcast (X .+ 1) 0.713 ± 0.046 ms 0.786 ± 0.073 ms 0.907 ± 0.1
array/dagger/N=256 (block 256)/map (sin.(X)) 1.22 ± 0.048 ms 1.09 ± 0.15 ms 1.12 ± 0.16
array/dagger/N=256 (block 256)/norm 0.63 ± 0.18 ms 0.703 ± 0.65 ms 0.896 ± 0.87
array/dagger/N=256 (block 256)/reduce (sum) 3.86 ± 2.9 ms 1.07 ± 1.8 ms 3.59 ± 6.6
array/dagger/N=256 (block 256)/transpose (permutedims) 1.11 ± 0.3 ms 1.04 ± 0.23 ms 1.07 ± 0.38
linalg/dagger/N=1024 (block 512)/cholesky 22.5 ± 9.8 ms 23.2 ± 1.8 ms 0.971 ± 0.43
linalg/dagger/N=1024 (block 512)/lu 0.0417 ± 0.0043 s 0.0414 ± 0.0059 s 1.01 ± 0.18
linalg/dagger/N=1024 (block 512)/matmul (A*A) 0.0567 ± 0.0043 s 0.0597 ± 0.017 s 0.95 ± 0.27
linalg/dagger/N=1024 (block 512)/matvec (A*x) 2.9 ± 0.23 ms 2.92 ± 0.23 ms 0.99 ± 0.11
linalg/dagger/N=1024 (block 512)/qr 0.113 ± 0.006 s 0.111 ± 0.0041 s 1.02 ± 0.066
linalg/dagger/N=1024 (block 512)/solve (A\b via lu) 0.0568 ± 0.0013 s 0.0531 ± 0.0042 s 1.07 ± 0.087
linalg/dagger/N=1024 (block 512)/svd 0.0365 h 0.0356 h 1.03
linalg/dagger/N=1024 (block 512)/syrk (A'*A) 0.0366 ± 0.0013 s 0.0374 ± 0.0056 s 0.978 ± 0.15
linalg/dagger/N=256 (block 256)/cholesky 4.6 ± 1.7 ms 4.35 ± 2.6 ms 1.06 ± 0.75
linalg/dagger/N=256 (block 256)/lu 5.1 ± 1.2 ms 4.18 ± 1 ms 1.22 ± 0.42
linalg/dagger/N=256 (block 256)/matmul (A*A) 2.78 ± 1.4 ms 2.42 ± 0.23 ms 1.15 ± 0.59
linalg/dagger/N=256 (block 256)/matvec (A*x) 1.27 ± 0.075 ms 1.33 ± 0.27 ms 0.952 ± 0.2
linalg/dagger/N=256 (block 256)/qr 5.12 ± 1.2 ms 5.03 ± 0.27 ms 1.02 ± 0.25
linalg/dagger/N=256 (block 256)/solve (A\b via lu) 9.37 ± 0.51 ms 18.9 ± 6.3 ms 0.496 ± 0.17
linalg/dagger/N=256 (block 256)/svd 0.553 ± 0.039 s 0.519 ± 0.0084 s 1.07 ± 0.077
linalg/dagger/N=256 (block 256)/syrk (A'*A) 3.9 ± 2.6 ms 4.26 ± 2 ms 0.917 ± 0.75
stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) 8.87 ± 0.69 ms 9.43 ± 0.47 ms 0.94 ± 0.087
stencil/dagger/N=1024 (block 512)/assign (const) 1.39 ± 0.15 ms 1.41 ± 0.063 ms 0.99 ± 0.12
stencil/dagger/N=1024 (block 512)/multi-expr 3.8 ± 0.6 ms 4.24 ± 1.3 ms 0.896 ± 0.3
stencil/dagger/N=1024 (block 512)/neighbors (Clamp) 7.23 ± 1.5 ms 7.13 ± 0.65 ms 1.01 ± 0.23
stencil/dagger/N=1024 (block 512)/neighbors (Pad) 7.18 ± 0.53 ms 7.15 ± 0.52 ms 1.01 ± 0.1
stencil/dagger/N=1024 (block 512)/neighbors (Reflect) 7.64 ± 1.8 ms 7.01 ± 0.18 ms 1.09 ± 0.26
stencil/dagger/N=1024 (block 512)/neighbors (Wrap) 7.12 ± 1.8 ms 8.05 ± 0.58 ms 0.885 ± 0.23
stencil/dagger/N=1024 (block 512)/update (+) 2.7 ± 0.44 ms 2.26 ± 0.19 ms 1.2 ± 0.22
stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) 1.93 ± 0.087 ms 2.23 ± 0.29 ms 0.864 ± 0.12
stencil/dagger/N=256 (block 256)/assign (const) 0.648 ± 0.083 ms 0.779 ± 0.065 ms 0.832 ± 0.13
stencil/dagger/N=256 (block 256)/multi-expr 1.27 ± 0.55 ms 1.3 ± 0.069 ms 0.978 ± 0.43
stencil/dagger/N=256 (block 256)/neighbors (Clamp) 1.52 ± 0.14 ms 1.59 ± 0.12 ms 0.957 ± 0.12
stencil/dagger/N=256 (block 256)/neighbors (Pad) 1.57 ± 0.11 ms 1.76 ± 0.23 ms 0.892 ± 0.13
stencil/dagger/N=256 (block 256)/neighbors (Reflect) 1.54 ± 0.19 ms 1.77 ± 0.15 ms 0.866 ± 0.13
stencil/dagger/N=256 (block 256)/neighbors (Wrap) 1.49 ± 0.058 ms 1.71 ± 0.1 ms 0.87 ± 0.063
stencil/dagger/N=256 (block 256)/update (+) 0.909 ± 0.057 ms 1.01 ± 0.11 ms 0.903 ± 0.11
time_to_load 1.03 ± 0.0031 s 1.03 ± 0.042 s 0.994 ± 0.041

Plots

⚠️ Regressions (> 25.0%)

  • linalg/dagger/N=256 (block 256)/solve (A\b via lu): +101.8%
  • array/dagger/N=1024 (block 512)/norm: +25.8%

Improvements (> 25.0% faster)

  • array/dagger/N=256 (block 256)/reduce (sum): -72.2%
Distributed benchmarks (4 processes)

Dagger benchmarks: dirty vs master

master dirty master / dirty
array/dagger/N=1024 (block 512)/add (X + X) 6 ± 0.14 ms 6.15 ± 0.091 ms 0.975 ± 0.027
array/dagger/N=1024 (block 512)/alloc (rand) 5.81 ± 0.09 ms 5.87 ± 0.049 ms 0.991 ± 0.018
array/dagger/N=1024 (block 512)/broadcast (X .+ 1) 4.6 ± 0.038 ms 4.73 ± 0.074 ms 0.971 ± 0.017
array/dagger/N=1024 (block 512)/map (sin.(X)) 13.1 ± 0.028 ms 13.1 ± 0.051 ms 0.995 ± 0.0044
array/dagger/N=1024 (block 512)/norm 1.36 ± 0.07 ms 1.44 ± 0.12 ms 0.941 ± 0.092
array/dagger/N=1024 (block 512)/reduce (sum) 2.45 ± 0.076 ms 2.32 ± 0.14 ms 1.06 ± 0.072
array/dagger/N=1024 (block 512)/transpose (permutedims) 12.5 ± 0.048 ms 12 ± 0.13 ms 1.04 ± 0.012
array/dagger/N=256 (block 256)/add (X + X) 0.977 ± 0.015 ms 1.08 ± 0.11 ms 0.902 ± 0.095
array/dagger/N=256 (block 256)/alloc (rand) 1.25 ± 0.03 ms 1.28 ± 0.036 ms 0.974 ± 0.036
array/dagger/N=256 (block 256)/broadcast (X .+ 1) 0.574 ± 0.022 ms 0.479 ± 0.03 ms 1.2 ± 0.088
array/dagger/N=256 (block 256)/map (sin.(X)) 1.1 ± 0.0089 ms 1.2 ± 0.042 ms 0.922 ± 0.034
array/dagger/N=256 (block 256)/norm 0.413 ± 0.052 ms 0.472 ± 0.013 ms 0.875 ± 0.11
array/dagger/N=256 (block 256)/reduce (sum) 0.753 ± 0.044 ms 0.821 ± 0.043 ms 0.918 ± 0.073
array/dagger/N=256 (block 256)/transpose (permutedims) 1.02 ± 0.11 ms 1.2 ± 0.072 ms 0.852 ± 0.11
linalg/dagger/N=1024 (block 512)/cholesky 18.2 ± 0.29 ms 18.2 ± 0.29 ms 0.999 ± 0.023
linalg/dagger/N=1024 (block 512)/lu 0.0437 ± 0.0028 s 0.0426 ± 0.0022 s 1.03 ± 0.085
linalg/dagger/N=1024 (block 512)/matmul (A*A) 0.0339 ± 0.00016 s 0.0333 ± 0.00025 s 1.02 ± 0.009
linalg/dagger/N=1024 (block 512)/matvec (A*x) 3.2 ± 0.023 ms 2.9 ± 0.011 ms 1.1 ± 0.0091
linalg/dagger/N=1024 (block 512)/qr 0.105 ± 0.0012 s 0.104 ± 0.0021 s 1.02 ± 0.023
linalg/dagger/N=1024 (block 512)/solve (A\b via lu) 0.0448 ± 2.6e-05 s 0.0444 ± 0.00034 s 1.01 ± 0.0077
linalg/dagger/N=1024 (block 512)/svd 0.0368 h 0.0369 h 0.995
linalg/dagger/N=1024 (block 512)/syrk (A'*A) 0.0348 ± 0.0026 s 28.1 ± 0.29 ms 1.24 ± 0.093
linalg/dagger/N=256 (block 256)/cholesky 2.63 ± 0.086 ms 2.62 ± 0.013 ms 1 ± 0.033
linalg/dagger/N=256 (block 256)/lu 4.26 ± 0.2 ms 4.36 ± 0.17 ms 0.976 ± 0.059
linalg/dagger/N=256 (block 256)/matmul (A*A) 2.19 ± 0.18 ms 2.14 ± 0.063 ms 1.02 ± 0.088
linalg/dagger/N=256 (block 256)/matvec (A*x) 1.34 ± 0.023 ms 1.37 ± 0.066 ms 0.985 ± 0.05
linalg/dagger/N=256 (block 256)/qr 4.03 ± 0.11 ms 4.03 ± 0.076 ms 1 ± 0.033
linalg/dagger/N=256 (block 256)/solve (A\b via lu) 6.62 ± 0.1 ms 6.85 ± 0.13 ms 0.966 ± 0.024
linalg/dagger/N=256 (block 256)/svd 0.57 ± 0.032 s 0.547 ± 0.037 s 1.04 ± 0.092
linalg/dagger/N=256 (block 256)/syrk (A'*A) 2.92 ± 0.066 ms 2.8 ± 0.14 ms 1.04 ± 0.058
stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) 14.9 ± 0.17 ms 14.9 ± 0.094 ms 1 ± 0.013
stencil/dagger/N=1024 (block 512)/assign (const) 1.57 ± 0.083 ms 1.34 ± 0.046 ms 1.18 ± 0.074
stencil/dagger/N=1024 (block 512)/multi-expr 3.19 ± 0.31 ms 3.07 ± 0.1 ms 1.04 ± 0.11
stencil/dagger/N=1024 (block 512)/neighbors (Clamp) 10.5 ± 0.15 ms 10.3 ± 0.039 ms 1.02 ± 0.015
stencil/dagger/N=1024 (block 512)/neighbors (Pad) 10.6 ± 0.035 ms 10.3 ± 0.082 ms 1.03 ± 0.0089
stencil/dagger/N=1024 (block 512)/neighbors (Reflect) 11 ± 0.02 ms 10.3 ± 0.082 ms 1.07 ± 0.0087
stencil/dagger/N=1024 (block 512)/neighbors (Wrap) 11.2 ± 0.098 ms 11 ± 0.0087 ms 1.02 ± 0.0089
stencil/dagger/N=1024 (block 512)/update (+) 2.23 ± 0.039 ms 2.12 ± 0.02 ms 1.05 ± 0.021
stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) 1.95 ± 0.061 ms 1.69 ± 0.055 ms 1.15 ± 0.052
stencil/dagger/N=256 (block 256)/assign (const) 0.6 ± 0.054 ms 0.63 ± 0.0086 ms 0.953 ± 0.087
stencil/dagger/N=256 (block 256)/multi-expr 1.27 ± 0.098 ms 1.2 ± 0.0058 ms 1.05 ± 0.082
stencil/dagger/N=256 (block 256)/neighbors (Clamp) 1.63 ± 0.14 ms 1.5 ± 0.053 ms 1.08 ± 0.098
stencil/dagger/N=256 (block 256)/neighbors (Pad) 1.67 ± 0.039 ms 1.5 ± 0.022 ms 1.11 ± 0.031
stencil/dagger/N=256 (block 256)/neighbors (Reflect) 1.72 ± 0.055 ms 1.55 ± 0.052 ms 1.11 ± 0.051
stencil/dagger/N=256 (block 256)/neighbors (Wrap) 1.65 ± 0.033 ms 1.58 ± 0.059 ms 1.05 ± 0.044
stencil/dagger/N=256 (block 256)/update (+) 0.893 ± 0.095 ms 0.816 ± 0.0056 ms 1.09 ± 0.12
time_to_load 1.08 ± 0.0038 s 1.09 ± 0.01 s 0.994 ± 0.01

Plots

No regressions beyond 35.0% 🎉

MPI benchmarks (4 ranks)

Dagger benchmarks: dirty vs master

master dirty master / dirty
array/dagger/N=1024 (block 512)/add (X + X) 0.0362 ± 0.012 s
array/dagger/N=1024 (block 512)/alloc (rand) 10.3 ± 2.5 ms
array/dagger/N=1024 (block 512)/broadcast (X .+ 1) 4.28 ± 0.54 ms
array/dagger/N=1024 (block 512)/map (sin.(X)) 6.81 ± 0.5 ms
array/dagger/N=1024 (block 512)/norm 4.93 ± 0.059 ms
array/dagger/N=1024 (block 512)/reduce (sum) 7.25 ± 0.0062 ms
array/dagger/N=1024 (block 512)/transpose (permutedims) 27.2 ± 0.4 ms
array/dagger/N=256 (block 256)/add (X + X) 10.4 ± 0.082 ms
array/dagger/N=256 (block 256)/alloc (rand) 2.65 ± 0.054 ms
array/dagger/N=256 (block 256)/broadcast (X .+ 1) 1.32 ± 0.012 ms
array/dagger/N=256 (block 256)/map (sin.(X)) 2.05 ± 0.00059 ms
array/dagger/N=256 (block 256)/norm 1.91 ± 0.00094 ms
array/dagger/N=256 (block 256)/reduce (sum) 2.07 ± 0.033 ms
array/dagger/N=256 (block 256)/transpose (permutedims) 6.76 ± 0.011 ms
linalg/dagger/N=1024 (block 512)/cholesky 0.503 ± 0.019 s
linalg/dagger/N=1024 (block 512)/lu 0.0382 h
linalg/dagger/N=1024 (block 512)/matmul (A*A) 0.956 ± 0.012 s
linalg/dagger/N=1024 (block 512)/matvec (A*x) 0.0648 ± 0.00054 s
linalg/dagger/N=1024 (block 512)/qr 0.991 ± 0.054 s
linalg/dagger/N=1024 (block 512)/solve (A\b via lu) 0.0359 h
linalg/dagger/N=1024 (block 512)/syrk (A'*A) 0.778 ± 0.0074 s
linalg/dagger/N=256 (block 256)/cholesky 0.0372 ± 0.022 s
linalg/dagger/N=256 (block 256)/lu 19.8 s
linalg/dagger/N=256 (block 256)/matmul (A*A) 16.3 ± 2.8 ms
linalg/dagger/N=256 (block 256)/matvec (A*x) 18.1 ± 2.8 ms
linalg/dagger/N=256 (block 256)/qr 0.0474 ± 0.00096 s
linalg/dagger/N=256 (block 256)/solve (A\b via lu) 20.5 s
linalg/dagger/N=256 (block 256)/syrk (A'*A) 0.128 ± 0.00099 s
stencil/dagger/N=1024 (block 512)/alloc (neighbors Wrap) 0.168 ± 0.00076 s
stencil/dagger/N=1024 (block 512)/assign (const) 11.3 ± 0.2 ms
stencil/dagger/N=1024 (block 512)/multi-expr 28.3 ± 0.31 ms
stencil/dagger/N=1024 (block 512)/neighbors (Clamp) 0.155 ± 0.0012 s
stencil/dagger/N=1024 (block 512)/neighbors (Pad) 0.152 ± 0.001 s
stencil/dagger/N=1024 (block 512)/neighbors (Reflect) 0.156 ± 0.0044 s
stencil/dagger/N=1024 (block 512)/neighbors (Wrap) 0.163 ± 0.0016 s
stencil/dagger/N=1024 (block 512)/update (+) 17.9 ± 0.038 ms
stencil/dagger/N=256 (block 256)/alloc (neighbors Wrap) 23.8 ± 4.1 ms
stencil/dagger/N=256 (block 256)/assign (const) 3.5 ± 0.052 ms
stencil/dagger/N=256 (block 256)/multi-expr 8.29 ± 0.23 ms
stencil/dagger/N=256 (block 256)/neighbors (Clamp) 17.2 ± 0.026 ms
stencil/dagger/N=256 (block 256)/neighbors (Pad) 16.9 ± 0.08 ms
stencil/dagger/N=256 (block 256)/neighbors (Reflect) 17 ± 0.21 ms
stencil/dagger/N=256 (block 256)/neighbors (Wrap) 17.3 ± 0.02 ms
stencil/dagger/N=256 (block 256)/update (+) 4.91 ± 0.14 ms
time_to_load 1.1 ± 0.0043 s 1.1 ± 0.0011 s 0.993 ± 0.004

No regressions beyond 35.0% 🎉

Full results and plots (download the benchmark-results-* artifacts).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant