Skip to content

Float32 results from the Workspace; cheaper range check in the AVX2 log - #94

Merged
tannevaled merged 2 commits into
mainfrom
perf/log32-api-gap
Nov 7, 2026
Merged

tannevaled merged 2 commits into
mainfrom
perf/log32-api-gap

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Float32 results now come from the Workspace, and the AVX2 log kernel's range check is cheaper. This also corrects a README claim made in v0.34.0.

What was wrong: through the API, float32 Log was 1.25× NumPy at 16 Ki on Zen 3, although its kernel was at parity. The README said it ran "as fast as NumPy".

  • The gap was the result's allocation: make alone costs 4.1 µs at 16 Ki and 130 µs at 1 Mi on that core (a large span from the heap, zeroed). Kernel plus allocation adds up to the API time at both sizes.
  • The data was not the cause: on the API benchmark's data the kernel hands back 0 blocks (new handed-blocks/op metric).
  • The Workspace, the library's answer to allocation, only handed out float64, so a Float32 result went to the heap even inside one.

Changes:

  • Workspace.alloc32 carves float32 storage from the same blocks: two per float64 element, 64-byte aligned, cap = len. It is used by Exp, Log, the other float32 ufuncs, arithmetic, Power, Clip and the float32 GEMM (zeroed there, because it accumulates).
  • TestWorkspace32MatchesHeap holds every site to the heap result bit for bit, including on an arena filled with NaN bits. Making the GEMM's allocation unzeroed makes it fail (checked). TestWorkspaceAlloc32Shape checks length, cap, alignment and zeroing; TestWorkspace32Allocates checks the allocation count.
  • log32CRAVX2 rejects lanes that are not positive normals at the end of the block. One wrapping add and one compare (ux + 0x7f800000 ≤ −0x01000001 as int32, exact because adding a constant mod 2³² is a bijection) replace five instructions and a branch. That is −2.4% (abbench, 8 rounds, p ≤ 0.03 on three of four sizes).
  • Re-proven on Zen 3: TestLog32CorrectlyRounded over all 2³² inputs finds 0 incorrectly rounded.
float32, one Zen 3 core, load ≤ 4.2 16 Ki 1 Mi
Exp in a Workspace 19.4 µs 1.23 ms
NumPy 2.5.4 np.exp 22.0–22.3 µs 1.37–1.47 ms
Log, allocating (v0.34.0: 35.5 µs) 34.5 µs 2.06 ms
Log in a Workspace 29.9 µs 1.90 ms
NumPy 2.5.4 np.log 28.1–28.8 µs 1.77–1.82 ms

In a Workspace, Exp is about 12% faster than NumPy and Log about 5% slower. The README and docs/perf.md now say exactly that. Coverage of the package stays at 100%. SECURITY.md has the audit entry for the unsafe.Slice in alloc32.

🤖 Generated with Claude Code

tannevaled and others added 2 commits November 7, 2026 20:31
Through the API, float32 Log was 1.25x NumPy at 16 Ki on Zen 3 although
its kernel was at parity: the result's make costs 4.1 us there (a large
span taken from the heap and zeroed), and the Workspace only handed out
float64, so a Float32 result went to the heap even inside one.

Workspace.alloc32 carves float32 storage from the same blocks (two per
float64 element, 64-byte aligned, cap = len). unary32 (Exp, Log), the
float32 ufuncs, arithmetic, Power, Clip and the float32 GEMM (zeroed) use
it. TestWorkspace32MatchesHeap holds every site to the heap bit for bit,
also on a NaN-poisoned arena; an unzeroed GEMM allocation fails it. In a
Workspace, float32 Log goes from 35.3 to 30.6 us at 16 Ki.

log32CRAVX2 now rejects non-positive-normal lanes at the end of the block
with one wrapping add and one compare, instead of a five-instruction test
and a branch before the arithmetic: -2.4% (abbench, 8 rounds).

BenchmarkLog32Kernels gains the API's data set, 1 Mi, and the count of
blocks handed back to logCR; BenchmarkDTypeExpLogWS is new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on one Zen 3 core with the load at most 4.2: in a Workspace,
float32 Exp is about 12% faster than NumPy and Log about 5% slower. The
README no longer claims API-level parity for both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit a85117f into main Nov 7, 2026
12 checks passed
@tannevaled
tannevaled deleted the perf/log32-api-gap branch November 7, 2026 19:44
tannevaled added a commit that referenced this pull request Nov 7, 2026
…xp, log (K1) (#97)

* kernels: import go-simd/floats for sum, elementwise, extrema, clip, exp, log (K1)

K1 of the go-scikit performance campaign (go-scikit/roadmap#27): ndarray now
imports go-simd/floats v0.9.0 and deletes its copies.

- exp, log, and the correctly rounded float32 exp/log (with the AVX2 kernels of
  #94/#96 and the NEON kernel of #95, now go-simd/floats#29): deleted here
  (Go sources, tests, generator kernels, assembly); Exp, Log, Exp32, Log32 and
  their P forms call floats.
- amd64 and arm64: the sum/sqrt/max/min/add/sub/mul/div kernels are floats';
  Clip is floats.ClampScalar. Max/Min resolve a zero result with the serial
  oracle (floats leaves the sign of zero unspecified).
- Sum is floats.SumPairwise on every architecture: NumPy's order, bit-identical
  to np.sum; serial at every size. A changed last bit is the fix; the golden
  values of TestSumMatchesNumPy are NumPy 2.5.4's.
- Not moved: the float32 elementwise/sqrt/extremum/AddBlocks8F32 kernels, the
  dot inside GEMM/Dot1D, and the ppc64le, loong64, riscv64 and s390x kernels
  (floats has none for them yet).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* kernels: test the parallel exp/log forms and Log10 (100% coverage gate)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant