Uh oh!
There was an error while loading. Please reload this page.
Regulariser dispatch: one per-surface header instead of six copies - #158
Conversation
The regulariser API surface -- `reg_field` (multi-channel fields) and
`reg_flow` (vector flows) -- carried its ndim x bound x dtype x index-width
dispatch pyramid as per-file macros. Measured across the six regulariser
translation units on main:
* BND1 / BND2 / BND3 and BOUND_SWITCH are byte-identical in all six.
* NDIM_SWITCH differs in exactly one word: the noun in its diagnostic,
"field" versus "flow".
* The dtype x index-width fan is copied once per ENTRY POINT rather than
once per module -- thirteen times in reg_field.cpp alone, fifty-two
across the six, out of seventy-one in the whole tree. Each copy differs
only in the leaf's name and, for nine of them, one extra `char`
template argument.
This moves that pyramid into include/fastfields/api/cpu/reg_dispatch.h, the
same shape and location as the existing api/{cpu,cuda}/pushpull_dispatch.h,
and converts the two CPU regulariser translation units to it. The fan is now
written twice (once per leaf shape: with and without the compile-time op
char) instead of fifty-two times; the boundary and rank switches once each.
Behaviour is unchanged by construction. The FF_BOUND_<NAME> selectors, the
switch labels, the boundary-pack lengths and every diagnostic string are the
same tokens they were; only their location moved.
Instantiation-neutral, which is the constraint that matters here -- the leaf
set is what ptxas has to process on the CUDA side. clang++ -O3 -fPIC with the
CUDA shipping bound policy, counting defined text/weak symbols:
reg_field main 10,140 this 10,140 (identical; object 19,533,368 ->
19,533,360 bytes)
Argument lists use ISO `...` / `__VA_ARGS__` rather than the GNU named
variadic `args...` the other dispatch macros still use. That is a
portability down payment and not a fix: MSVC's traditional preprocessor
also mis-forwards __VA_ARGS__ into a nested macro, so an MSVC build would
need /Zc:preprocessor regardless. Nothing here builds under MSVC and that
is untested.
Scope is deliberately the CPU side only. The four CUDA regulariser TUs want
the mirror header, but src/lib-cuda/reg_flow.cpp is being rewritten by the
per-(family, ndim) TU split, so that half should follow rather than collide
with it.
C++11. Clean under clang++ and g++ at -std=c++11.balbasty
commented
Aug 20, 2026
The
So the leaf set — the thing ptxas has to process once the CUDA mirror follows — Restating the caveat from the description, because this run made it vivid: the Generated by Claude Code |
balbasty
commented
Aug 20, 2026
Chasing the eight-byte object-size difference on It is not code. Same build as above: Identical symbol sets — all 22,909, name for name — and identical Combined with (Method note for anyone repeating it: Generated by Claude Code |
balbasty
commented
Aug 20, 2026
Local gate, as promised in the description. 13 suites, 59,886 checks, 0 failures, plus the 2 hub suites (14 checks) — Note for anyone re-running it:
CI's Generated by Claude Code |
The header said nine dispatch sites thread a compile-time `char` op and seven do not. Counted properly it is eight and five, of thirteen per file: with op addmatvec_, submatvec_, diag, adddiag_, subdiag_, kernel, addkernel_, subkernel_ without op matvec, relax, matvec_rls, diag_rls, relax_rls Comment only; no code change.
Uh oh!
There was an error while loading. Please reload this page.
Answers points 3 and 5 of the review on #94. Replaces that PR's
include/fastfields/api/dispatch.h, which was correctly called out asmis-named: its content was tied to
reg_field's template order, andcore/dispatch.halready owns the name for the genuinely surface-independenthelpers.
What this is worth on its own
Readability, and nothing else — that is the whole claim. It is measured
instantiation-neutral (below), so it makes no memory argument and should not be
reviewed as if it did. What it buys is that the regulariser dispatch pyramid is
stated once instead of six times, and its innermost fan twice instead of
fifty-two times.
The measurement that motivated it
Counting the dtype × index-width fan — the innermost 2×2 every dispatch site
ends in — by where the copies live on
main:off32_tmentions73% of the duplication is in this one surface, because it copies the fan once
per entry point rather than once per module — thirteen times in
reg_field.cppalone. Every other surface has one to three copies in total,which is why this is a per-surface header and not a general dispatcher; the
reasoning is written out in the #94 reply.
The rest of the pyramid is near-exact across the six regulariser TUs
(
src/lib-cpu/reg_{field,flow}.cpp,src/lib-cuda/reg_{field,flow}{,_rls}.cpp):BND1/BND2/BND3— byte-identical in all six.BOUND_SWITCH— byte-identical in all six.NDIM_SWITCH— differs in exactly one word, the noun in its diagnostic:"Only 1D, 2D and 3D field are supported" versus "… flow …". That word
is now the
NOUNparameter.What the header does
include/fastfields/api/cpu/reg_dispatch.h, the same shape and location as theexisting
api/{cpu,cuda}/pushpull_dispatch.h:FF_REG_BND1/2/3— the boundary pack, unchanged text.FF_REG_DT/FF_REG_DT_OP— the dtype × index fan, twice. Two and notone because the surface genuinely has two leaf shapes. Of the thirteen
dispatch sites in each file, eight thread a compile-time
charop as theleaf's second template argument (
addmatvec_,submatvec_, anddiag/kernelin all three variants) and five have no op concept at all(plain
matvec,relax, and the three_rlsops). Both macros takeOPsothe levels above can forward one argument list to either;
FF_REG_DTignoresit.
FF_REG_BOUND,FF_REG_NDIM— once each.FF_DISPATCH_REG(FN, NOUN, …)/FF_DISPATCH_REG_OP(FN, OP, NOUN, …)— thetwo call forms.
Call sites go from a
#define …_ARGS/NDIM_SWITCH(…_DT)/#undeftriple toone call with an ordinary argument list:
26 sites converted, 13 per file. Net −473 lines across the two TUs against
+176 for the header.
Behaviour is unchanged, and this is checkable at the object level
The
FF_BOUND_<NAME>selectors, the switch labels, the boundary-pack lengthsand every diagnostic string are the same tokens they were; only their location
moved. The
BOUNDFLAGSstatic/dynamic routing is untouched.clang++ -std=c++11 -O3 -fPICwith the CUDA shipping bound policy(
-DFF_STATIC_BOUNDS=0 -DFF_STATIC_BOUND_DCT2=1 -DFF_STATIC_BOUND_DST2=1),counting defined text/weak symbols:
reg_field.cppmainreg_flow.cppmainreg_flow's object is byte-identical.reg_fieldhas identical symbolsets (all 22,909, name for name) and identical
text/data/bss— theeight-byte file difference is non-loadable metadata. Details in the comments
below.
Instantiation-neutrality is the constraint that matters here: the leaf set is
what ptxas has to process once the CUDA mirror follows.
I am deliberately not quoting compile time or peak RSS. That run had three
builds sharing four cores and the timings were not reproducible (
reg_flowmeasured 216 s on
mainand 1,929 s here — an artefact, not a regression).Instantiation count and object size are deterministic under contention; time is
not.
Variadic macro syntax
Argument lists use ISO
.../__VA_ARGS__rather than the GNU named-variadicargs...the other dispatch macros still use. This is a portability downpayment, not an MSVC fix: MSVC's traditional preprocessor also mis-forwards
__VA_ARGS__into a nested macro (it arrives as one argument), so a nestedpyramid would need
/Zc:preprocessorregardless. Nothing in this tree buildsunder MSVC and I have not tested any of that — there is no MSVC here.
Scope: CPU only, on purpose
The four CUDA regulariser TUs want the mirror header and it is a mechanical
follow-up. It is not in this PR because
src/lib-cuda/reg_flow.cppis beingrewritten by #147's per-(family, ndim) TU split, and #147 has independently
grown its own copy of this pyramid (
src/lib-cuda/reg_flow_slice.inl, withFF_FLOW_BND1/2/3,FF_FLOW_MV_DT, …). Those two should be reconciled once#147 lands, rather than conflicting now.
Because this touches only
api/cpu/andsrc/lib-cpu/, CI's path filtercorrectly skips the CUDA legs.
Gate
Local: 13 suites, 59,886 checks, 0 failures, plus the 2 hub suites, row for
row identical to
tools/test-baseline.expected— full rows in a comment below,including a note on why
--checkexits 1 on a two-leg run even when every rowmatches.
CI: all
test-cpulegs, both lint jobs andcodespellgreen.Related: #160 records something found while measuring for this PR —
resize/restrict/splincare outside theBOUNDFLAGSpolicy entirely.Independent of this change.