SUMPROD¶
The SUMPROD cost is the per-call base price of math.sumprod, measured as the latency
difference between a probe chaining the extended-precision sum of two 2-element products and
one chaining only tmp + x[i] — probes f_add_sumprod2 and f_add. Together with
SUMPROD_XELEM (the per-extra-element slope) it prices sumprod at any
length. The probe is a faithful port of the compensated (TripleLength) accumulation CPython's
math.sumprod runs on exact-float inputs: per element a dl_mul error-free product — the
error term a genuine fused multiply-add, emitted through the llvm.fma intrinsic — folded
into a three-double running total, plus the close-out collapsing that total to one double.
The compensated sequence has real instruction-level parallelism (only the hi-lane
accumulation is serial), which is why the cost is measured on the whole algorithm rather than
decomposed into a per-operation chain — see Cost-model rules.
What Python code counts into SUMPROD is described in
FLOP types.
Inner-loop diff¶
--- f_add
+++ f_add_sumprod2
.L0:
- ldr %d0, [%x0], #8
- fadd %d1, %d1, %d0
- str %d1, [%x1], #8
- subs %x2, %x2, #1
+ ldr %d0, [%x0, #8]!
+ fmul %d1, %d0, %d2
+ fadd %d3, %d3, %d0
+ ldr %d0, [%x1]
+ fmul %d4, %d3, %d0
+ fnmsub %d3, %d3, %d0, %d4
+ fadd %d0, %d1, %d4
+ fsub %d5, %d0, %d1
+ fsub %d6, %d0, %d5
+ fsub %d6, %d1, %d6
+ fsub %d4, %d4, %d5
+ fadd %d4, %d4, %d6
+ fadd %d5, %d1, %d3
+ fsub %d6, %d5, %d1
+ fsub %d7, %d5, %d6
+ fsub %d7, %d1, %d7
+ fsub %d3, %d3, %d6
+ fadd %d3, %d3, %d7
+ fadd %d6, %d5, %d4
+ fsub %d7, %d6, %d5
+ fsub %d8, %d6, %d7
+ fsub %d5, %d5, %d8
+ fsub %d4, %d4, %d7
+ fadd %d4, %d4, %d5
+ fadd %d3, %d3, %d4
+ fadd %d3, %d1, %d3
+ ldp %d4, %d1, [%x1, #-16]
+ fmul %d5, %d1, %d4
+ fnmsub %d1, %d1, %d4, %d5
+ fadd %d4, %d0, %d5
+ fsub %d7, %d4, %d0
+ fsub %d8, %d4, %d7
+ fsub %d0, %d0, %d8
+ fsub %d5, %d5, %d7
+ fadd %d0, %d5, %d0
+ fadd %d5, %d1, %d6
+ fsub %d7, %d5, %d6
+ fsub %d8, %d5, %d7
+ fsub %d6, %d6, %d8
+ fsub %d1, %d1, %d7
+ fadd %d1, %d1, %d6
+ fadd %d6, %d5, %d0
+ fsub %d7, %d6, %d5
+ fsub %d8, %d6, %d7
+ fsub %d5, %d5, %d8
+ fsub %d0, %d0, %d7
+ fadd %d0, %d0, %d5
+ fadd %d0, %d1, %d0
+ fadd %d3, %d3, %d0
+ fadd %d0, %d4, %d6
+ fsub %d1, %d0, %d6
+ fsub %d5, %d0, %d1
+ fsub %d5, %d6, %d5
+ fsub %d1, %d4, %d1
+ fadd %d1, %d1, %d5
+ fadd %d3, %d1, %d3
+ fadd %d3, %d0, %d3
+ str %d3, [%x2], #8
+ mov %x1, %x0
+ subs %x3, %x3, #1
b.ne .L0
Loop structure¶
f_add-- 2 innermost loop(s): 30 instructions, 6 instructionsf_add_sumprod2-- 2 innermost loop(s): 65 instructions, 62 instructions
The listings below are the complete compiled functions the benchmark times, raw as numba emits them (the cpython call wrappers around them are omitted -- they never run inside the timed loop). Listing lengths reflect the compiler's unrolling choices, not the probes' amount of work -- see the discussion below.
Full ASM listing: f_add
cmp x2, #1
b.lt LBB0_11
subs x8, x3, #1
b.lt LBB0_11
ldr x9, [sp, #56]
ldr x10, [sp]
and x11, x3, #0x7
and x12, x3, #0x7ffffffffffffff8
mov x13, #22377
movk x13, #35604, lsl #16
movk x13, #48906, lsl #32
movk x13, #16389, lsl #48
fmov d0, x13
b LBB0_4
LBB0_3:
subs x2, x2, #1
b.le LBB0_11
LBB0_4:
cmp x8, #7
b.hs LBB0_6
mov x13, #0
mov.16b v1, v0
b LBB0_9
LBB0_6:
mov x13, #0
add x14, x10, #32
add x15, x9, #32
mov.16b v1, v0
LBB0_7:
ldur d2, [x14, #-32]
fadd d1, d1, d2
stur d1, [x15, #-32]
ldur d2, [x14, #-24]
fadd d1, d1, d2
stur d1, [x15, #-24]
ldur d2, [x14, #-16]
fadd d1, d1, d2
stur d1, [x15, #-16]
ldur d2, [x14, #-8]
fadd d1, d1, d2
stur d1, [x15, #-8]
ldr d2, [x14]
fadd d1, d1, d2
str d1, [x15]
ldr d2, [x14, #8]
fadd d1, d1, d2
str d1, [x15, #8]
ldr d2, [x14, #16]
fadd d1, d1, d2
str d1, [x15, #16]
ldr d2, [x14, #24]
fadd d1, d1, d2
str d1, [x15, #24]
add x15, x15, #64
add x14, x14, #64
add x13, x13, #8
cmp x12, x13
b.ne LBB0_7
cbz x11, LBB0_3
LBB0_9:
lsl x14, x13, #3
add x13, x9, x14
add x14, x10, x14
mov x15, x11
LBB0_10:
ldr d2, [x14], #8
fadd d1, d1, d2
str d1, [x13], #8
subs x15, x15, #1
b.ne LBB0_10
b LBB0_3
LBB0_11:
str xzr, [x0]
mov w0, #0
ret
Full ASM listing: f_add_sumprod2
cmp x2, #1
b.lt LBB0_9
cmp x3, #1
b.lt LBB0_9
ldr x8, [sp, #56]
ldp x10, x9, [sp]
sub x11, x10, #8
sub x12, x10, #16
sub x13, x10, #24
movi.2d v0, #0000000000000000
mov x14, #22377
movk x14, #35604, lsl #16
movk x14, #48906, lsl #32
movk x14, #16389, lsl #48
fmov d1, x14
b LBB0_4
LBB0_3:
subs x2, x2, #1
b.le LBB0_9
LBB0_4:
ldr d2, [x10]
fmul d3, d2, d0
fadd d2, d2, d1
ldr d4, [x11, x9, lsl #3]
fmul d5, d2, d4
fnmsub d2, d2, d4, d5
fadd d4, d3, d5
fsub d6, d4, d3
fsub d7, d4, d6
fsub d7, d3, d7
fsub d5, d5, d6
fadd d5, d5, d7
fadd d6, d3, d2
fsub d7, d6, d3
fsub d16, d6, d7
fsub d16, d3, d16
fsub d2, d2, d7
fadd d2, d2, d16
fadd d7, d6, d5
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d5, d5, d16
fadd d5, d5, d6
fadd d2, d2, d5
fadd d2, d3, d2
ldr d3, [x12, x9, lsl #3]
ldr d5, [x13, x9, lsl #3]
fmul d6, d3, d5
fnmsub d3, d3, d5, d6
fadd d5, d4, d6
fsub d16, d5, d4
fsub d17, d5, d16
fsub d4, d4, d17
fsub d6, d6, d16
fadd d4, d6, d4
fadd d6, d3, d7
fsub d16, d6, d7
fsub d17, d6, d16
fsub d7, d7, d17
fsub d3, d3, d16
fadd d3, d3, d7
fadd d7, d6, d4
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d4, d4, d16
fadd d4, d4, d6
fadd d3, d3, d4
fadd d2, d2, d3
fadd d3, d5, d7
fsub d4, d3, d7
fsub d6, d3, d4
fsub d6, d7, d6
fsub d4, d5, d4
fadd d4, d4, d6
fadd d2, d4, d2
fadd d2, d3, d2
str d2, [x8]
cmp x3, #1
b.eq LBB0_3
ldp d4, d3, [x10]
fmul d5, d3, d0
fadd d2, d2, d3
fmul d3, d2, d4
fnmsub d2, d2, d4, d3
fadd d4, d5, d3
fsub d6, d4, d5
fsub d7, d4, d6
fsub d7, d5, d7
fsub d3, d3, d6
fadd d3, d3, d7
fadd d6, d5, d2
fsub d7, d6, d5
fsub d16, d6, d7
fsub d16, d5, d16
fsub d2, d2, d7
fadd d2, d2, d16
fadd d7, d6, d3
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d3, d3, d16
fadd d3, d3, d6
fadd d2, d2, d3
fadd d2, d5, d2
ldr d3, [x11, x9, lsl #3]
ldr d5, [x12, x9, lsl #3]
fmul d6, d3, d5
fnmsub d3, d3, d5, d6
fadd d5, d4, d6
fsub d16, d5, d4
fsub d17, d5, d16
fsub d4, d4, d17
fsub d6, d6, d16
fadd d4, d6, d4
fadd d6, d3, d7
fsub d16, d6, d7
fsub d17, d6, d16
fsub d7, d7, d17
fsub d3, d3, d16
fadd d3, d3, d7
fadd d7, d6, d4
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d4, d4, d16
fadd d4, d4, d6
fadd d3, d3, d4
fadd d2, d2, d3
fadd d3, d5, d7
fsub d4, d3, d7
fsub d6, d3, d4
fsub d6, d7, d6
fsub d4, d5, d4
fadd d4, d4, d6
fadd d2, d4, d2
fadd d2, d3, d2
str d2, [x8, #8]
cmp x3, #2
b.eq LBB0_3
ldp d3, d5, [x10, #8]
fmul d4, d5, d0
fadd d2, d2, d5
ldr d5, [x10]
fmul d6, d2, d3
fnmsub d2, d2, d3, d6
fadd d3, d4, d6
fsub d7, d3, d4
fsub d16, d3, d7
fsub d16, d4, d16
fsub d6, d6, d7
fadd d6, d6, d16
fadd d7, d4, d2
fsub d16, d7, d4
fsub d17, d7, d16
fsub d17, d4, d17
fsub d2, d2, d16
fadd d2, d2, d17
fadd d16, d7, d6
fsub d17, d16, d7
fsub d18, d16, d17
fsub d7, d7, d18
fsub d6, d6, d17
fadd d6, d6, d7
fadd d2, d2, d6
fadd d2, d4, d2
ldr d4, [x11, x9, lsl #3]
fmul d6, d5, d4
fnmsub d4, d5, d4, d6
fadd d5, d3, d6
fsub d7, d5, d3
fsub d17, d5, d7
fsub d3, d3, d17
fsub d6, d6, d7
fadd d3, d6, d3
fadd d6, d4, d16
fsub d7, d6, d16
fsub d17, d6, d7
fsub d16, d16, d17
fsub d4, d4, d7
fadd d4, d4, d16
fadd d7, d6, d3
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d3, d3, d16
fadd d3, d3, d6
fadd d3, d4, d3
fadd d2, d2, d3
fadd d3, d5, d7
fsub d4, d3, d7
fsub d6, d3, d4
fsub d6, d7, d6
fsub d4, d5, d4
fadd d4, d4, d6
fadd d2, d4, d2
fadd d2, d3, d2
str d2, [x8, #16]
cmp x3, #3
b.eq LBB0_3
add x17, x10, #16
sub x14, x3, #3
add x15, x8, #24
mov x16, x17
LBB0_8:
ldr d3, [x16, #8]!
fmul d4, d3, d0
fadd d2, d2, d3
ldr d3, [x17]
fmul d5, d2, d3
fnmsub d2, d2, d3, d5
fadd d3, d4, d5
fsub d6, d3, d4
fsub d7, d3, d6
fsub d7, d4, d7
fsub d5, d5, d6
fadd d5, d5, d7
fadd d6, d4, d2
fsub d7, d6, d4
fsub d16, d6, d7
fsub d16, d4, d16
fsub d2, d2, d7
fadd d2, d2, d16
fadd d7, d6, d5
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d5, d5, d16
fadd d5, d5, d6
fadd d2, d2, d5
fadd d2, d4, d2
ldp d5, d4, [x17, #-16]
fmul d6, d4, d5
fnmsub d4, d4, d5, d6
fadd d5, d3, d6
fsub d16, d5, d3
fsub d17, d5, d16
fsub d3, d3, d17
fsub d6, d6, d16
fadd d3, d6, d3
fadd d6, d4, d7
fsub d16, d6, d7
fsub d17, d6, d16
fsub d7, d7, d17
fsub d4, d4, d16
fadd d4, d4, d7
fadd d7, d6, d3
fsub d16, d7, d6
fsub d17, d7, d16
fsub d6, d6, d17
fsub d3, d3, d16
fadd d3, d3, d6
fadd d3, d4, d3
fadd d2, d2, d3
fadd d3, d5, d7
fsub d4, d3, d7
fsub d6, d3, d4
fsub d6, d7, d6
fsub d4, d5, d4
fadd d4, d4, d6
fadd d2, d4, d2
fadd d2, d3, d2
str d2, [x15], #8
mov x17, x16
subs x14, x14, #1
b.ne LBB0_8
b LBB0_3
LBB0_9:
str xzr, [x0]
mov w0, #0
ret
Discussion¶
The subtraction isolates the whole 2-element compensated sum-of-products — inline code, no library call.
- What the diff shows: on top of
f_add's load/faddskeleton, the additions are the algorithm itself. Per element: the product (fmul), its error term as a single fusedfnmsub(LLVM lowersfma(x, y, -hi)to one fused negate-multiply-subtract), and three six-instruction error-free sums (fadd/fsubladders) folding the pair into the three-double running total. After both elements, the close-out: one more error-free sum plus the two final adds. The extrafmulagainst a constant zero is the probe's guard against constant folding: the accumulator starts from a runtime zero, so LLVM cannot fold the first element's compensated arithmetic the way CPython's runtime zeros never would be. - In the dependency chain: the hi-lane accumulation serializes across the two elements and
through the close-out, whose result feeds the next iteration's first product. The lo and
tiny lanes run beside it — the algorithm's genuine instruction-level parallelism — so the
measurement prices the algorithm as it really executes, not a serialized per-operation
decomposition (which is exactly why
SUMPRODis measured rather than decomposed). - Loop-structure symmetry:
f_addunrolls 8×; the sumprod loop, far larger, does not. Its two regions are the peeled first iteration (where the negative wraparound indices need separate handling) and the steady state. Both sides remain latency-bound, so the subtraction holds.