2 Commits
Author SHA1 Message Date
Uladzimir KarpenkaandClaude Opus 4.8 645cbbb2d1 varsamp: transpose the coefficients into phases, vectorize the tap loop
hshift() rebuilds the whole interpolated tap set on every output sample,
reading h[hidx + m*R] for m = 0..rsize-1. With R = 1024 (what rmatch asks
for) that strides 8 KB at a time through a 1.1 MB table, so every one of
the 2*rsize reads is its own cache line. It cost more than the filter it
was feeding: 258 ns per output sample against 146 ns for the tap loop.

Store the coefficients transposed instead, hp[p*rsize + m] = h[p + m*R],
so the two phases hshift() interpolates between are each contiguous. R+1
phases are needed since it reads hidx and hidx+1, and h_offset is kept in
[0,1) by the caller so hidx <= R-1. The untransposed h is freed; the
impulse cache hands back a copy, so varsamp owns it. Net memory is
unchanged.

The tap loop had the same wrap test per tap as resample.c did, so split it
at the wrap and carry four independent accumulator pairs; the ring is split
into I/Q so the taps load unit-stride.

Note a->hs is rewritten by hshift() inside the sample loop, so it must not
be hoisted behind a restrict pointer in xvarsamp().

Measured on an Apple M1 Pro, 512-sample buffers, best of 5:

    48k -> 48k   varmode=0    138580 ns -> 37370 ns   3.71x
    48k -> 48k   varmode=1    138390 ns -> 32333 ns   4.28x
    48k -> 44.1k varmode=1    141473 ns -> 38220 ns   3.70x
    44.1k -> 48k varmode=1    152307 ns -> 45263 ns   3.36x

hshift() is numerically identical -- same coefficients, different layout.
Only the reassociated tap sum rounds differently: worst deviation 7.2e-16
over four rate configurations, an SNR of 344 dB. Driven end to end through
rmatch's public API, output SNR is 306 dB and total energy matches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:19:36 +03:00
Uladzimir Karpenka 89c8a0e2b5 first commit 2026-06-01 15:58:45 +03:00