meta(amr): forest performance — the coarse–fine exchange, not regridding, is the AMR tax
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 25/100
- Tipo de issue
- Refactorización
- Claridad
- Necesita aclaración
- Estado de actividad
- Tranquilo
- Stack tecnológico
- julia
- Área
- hpc, performance
Línea de trabajo
Start with examples/niederer_benchmark.jl and run the documented full or NIEDERER_SMOKE benchmark to reproduce the phase table and inspect examples/niederer_cost.png. Read the linked candidate issues #83, #42, #84, #85, #78, and #72 before choosing a focused task; this parent tracker is complete when the selected optimization is measured without changing the nine-point correctness results.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Status (2026-09-30): #42, #84 and #85 have landed; the phase tables below predate them and need a re-run.
The new Niederer 2011 benchmark example (examples/niederer_benchmark.jl) is the first 3D BlockForest in this repo and the first workload that times the adaptive path against a like-for-like uniform baseline. It came out 2.34× faster overall — but the phase breakdown says the overhead is not where I expected, so this issue records the numbers and the optimization candidates they point at.
What the benchmark is
Monodomain cardiac EP on a 20 × 7 × 3 mm slab, ten Tusscher–Panfilov 2006 epicardial kinetics (18 gating/concentration states per cell, carried through regrid! as one SVector{18}-valued BlockField), anisotropic diffusion D_T ∇² + (D_L − D_T) ∂ₓₓ, Godunov splitting with explicit Euler on both halves, dt = 0.01 ms to t = 60 ms. Base 48×16×8, blocksize = (8,8,8), maxlevel = 2, so the finest spacing is ≈0.1 mm — Niederer's reference resolution — and the equal-resolution uniform grid is 192 × 64 × 32 = 393,216 cells.
Correctness first, because the perf numbers only matter if the answer is right. The scored metric is activation time at nine fixed points. Against an independent finite-difference code at dx = 0.1 mm the mean |Δ| is 0.40 ms (max 0.80). Against a uniform forest at the same finest spacing, all nine points agree to 0.00 ms. Every 3D coarse–fine path — Martin–Cartwright interpolation, flux-matching restriction, the balance cascade, the regrid! transfer of both a scalar and an 18-component field — was previously unexercised, and it is exact here.
The cost breakdown
5 threads, macOS/aarch64. The example now times both runs and splits the wall clock by phase.
| phase | adaptive | uniform | speedup |
|---|---|---|---|
| diffusion | 19.1 s | 8.5 s | 0.44× |
| reaction | 12.1 s | 68.9 s | 5.70× |
| regrid | 1.9 s | — | — |
| total | 33.2 s | 77.5 s | 2.34× |
The forest carries 18.8% of the uniform cell count on average, so the identity is 5.3× fewer cell-updates ÷ 2.28× dearer per update = 2.34× net (75 ns vs 33 ns per cell-update). Slightly more than half the cell saving is handed back.
Two things fall out:
- Reaction scales as hoped. It is pointwise, tracks the cell count, and at 5.70× marginally beats the 5.3× cell ratio because the smaller working set is kinder to cache.
- Regridding is cheap. 120 regrids — marking, topology edit, transfer of two fields, schedule rebuild — cost 1.9 s, under 6% of the run. This is the thing I assumed would dominate, and it does not.
Diffusion is the problem: 2.2× slower on 5.3× fewer cells, a ~12× per-cell penalty.
Where the diffusion time goes
Timing the pieces directly on a representative adapted forest (166 leaves, levels 0–2, refined in a slab-crossing band the way the wavefront refines it) against the uniform one:
| adapted, 166 leaves | uniform, 768 leaves | |
|---|---|---|
halo_update! |
0.94 ms | 0.22 ms |
apply_bc! |
~0 ms | 0.02 ms |
one laplacian apply |
1.01 ms | 0.53 ms |
| the anisotropic apply | 2.06 ms | 1.07 ms |
| exchange share of an apply | 92% | 45% |
| per cell-update | 24.2 ns | 2.7 ns |
On an adapted forest the halo exchange is the operator — 92% of an apply is ghost-filling, because every coarse–fine face runs quadratic interpolation and flux-matching restriction instead of a slab copy. Per leaf that is ~20× the cost of a same-level exchange.
Optimization candidates
This issue is the parent tracker for AMR forest performance. The numbers above that ranked these candidates are the pre-#84/#85 phase tables — the 92% exchange share and the 0.44× diffusion figure both predate those fusions — so re-run examples/niederer_benchmark.jl and post the updated phase table before prioritizing the open items.
- #42 — make
GhostFillisbits with a static term count (the prerequisite for #84). - #84 —
_run_fills!made one broadcast pass per term: 19 terms per interpolation fill in 3D, 16 fills per coarse–fine face, ~300 broadcasts per face. Fused into one gather per fill. - #85 —
Addedexchanged once per operand on a forest: two identical exchanges per step for a two-leafAdded, which with the exchange at 92% of an apply roughly doubled diffusion cost (~9 s of the 33 s run, estimated). - #94 — the adjoint half of #84:
halo_update_adjoint!still runs one scatter-add broadcast per CSR term, and fusing it bit-identically needs a source-centric transposed CSR built at schedule time. - #78 — batch the coarse–fine flux rewrite into one device launch for packed
Diffusion. - #72 — regrid cadence, buffer zone, and
preparereuse across timesteps (where the per-stepBlockFieldallocation inexamples/monodomain_amr.jllives). - Measure packed-on-CPU:
PackedBlockFieldfor a regridding workload. The packed path's reported 30–95× wins are GPU numbers. This workload regrids every 50 steps, and the documented contract is that a packed field cannot be regridded — you regrid the reference field and re-pack. Nobody has measured whether 50 packed steps amortize a re-pack on CPU. Given #83 threads the same reference path, worth deciding before optimizing it twice.
Cross-reference — threading, tracked under #51: #83. The reference forest path is single-threaded end to end: grep -rn "Threads\.\|@spawn\|@threads" src/ returns nothing; the leaf sweeps and the exchange descriptor loops are serial for loops. Likely the single biggest lever; it shares the executor decision with #51, which is why it is parented there rather than here.
Reproducing
julia -t auto --project=examples examples/niederer_benchmark.jl # both runs, ~2 min
NIEDERER_SMOKE=true ... # seconds, reduced levels/duration
NIEDERER_SKIP_UNIFORM=true ... # adaptive only
The example prints the phase table and writes examples/niederer_cost.png (cells over time, and wall time by phase). Full write-up in docs/pages/benchmark.qmd.
One caveat on all of the above: these are single-machine, 5-thread, CPU, BlockField numbers at one problem size. The relative weight of exchange versus stencil will move with block size, level count and thread count, and #83 in particular could reorder the rest.
—
🤖 Claude ticked the boxes here. The stopwatch still points at the exchange.
claude-opus-5-5[1m] (high) · claude-code 2.1.285 · unreviewed
- Lenguaje dominante
- Julia
- Estrellas
- 3
- Forks
- 1
- Merge medio
- 1 d 28 min
- PR fusionados (30 d)
- 6
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de RallypointOne/MatrixFreeOperators.jl
-
Roadmap: open-issue priorityAbierto
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
-
enhancement
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
-
performance
Dificultad 5/5 Más de una semana Aptitud para principiantes 45/100
-
perf(forest): thread the reference BlockForest path — leaf sweeps and exchange descriptor loopsAbiertoperformance
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
-
enhancement
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
Todos los issues de RallypointOne/MatrixFreeOperators.jl
Issues similares
-
io
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 72/100
-
docs
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
JuliaGPU/GPUArrays.jl#837 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
Los mantenedores suelen responder en 1 día