Skip to content

huff0: align the decode inner loops to 64 bytes - #1236

Merged
klauspost merged 3 commits into
klauspost:masterfrom
honeycombio:lizf.huff0-pcalign
Sep 28, 2026
Merged

klauspost merged 3 commits into
klauspost:masterfrom
honeycombio:lizf.huff0-pcalign

Conversation

@lizthegrey

@lizthegrey lizthegrey commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Aligns the huff0 decode inner loops to 64 bytes. This makes their performance independent of where the linker happens to place the functions; it does not claim a speedup.

(1) Move the asm generator to the current avo arm64 fork (71a6b98)

The honeycombio/avo fork's arm64 lowering now supports the PCALIGN pseudo-instruction and passes it through to the arm64 output. The regenerated decompress_amd64.s and decompress_arm64.s are byte-identical. The newer avo requires newer golang.org/x modules, which require go 1.25.0 in _generate/go.mod.

(2) Align the decode inner loops to 64 bytes (7b131b4)

PCALIGN $64 is placed before the inner_loop of each 4X and 1X decode loop: 12 functions on amd64 and 6 on arm64. It also raises each function's alignment to 64.

  • Why: without it, a loop head lands wherever the linker puts the function. The same code has measured about ±2% apart between builds that differed only in what came before it, which is also why our A/B benchmarks pin every TEXT symbol to 64 bytes.
  • Cost: the padding runs once per outer iteration, i.e. once per batch of symbols.
  • Diff size: apart from the 18 PCALIGN $0x40 lines, the .s diff is column re-alignment in the arm64 printer's output, because PCALIGN is the longest mnemonic. The diff with -w shows only the added lines.

(3) Move the generator to avo with the PCALIGN label check (48b49bf)

The avo fork now fails generation if a label sits directly on a PCALIGN. That placement makes the amd64 assembler misroute the jump (golang/go#74648) or, since Go 1.26, loop forever when it widens a branch (golang/go#81792). None of this PR's PCALIGNs has a label before it, and the regenerated assembly is byte-identical.

The same change for the zstd sequence loops will follow separately. It touches the same generator lines as #1212, so it waits for that to merge.

Tests: go test ./huff0 ./zstd pass on amd64 (Zen 3) and arm64 (Neoverse-N1). With -tags noasm, huff0 passes on amd64 and zstd on arm64.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Huffman decompression now processes input and output in bounded batches on ARM64, accounting for available data and output capacity.
    • Decompression loops on AMD64 are aligned to 64-byte boundaries, which may improve processing efficiency.
    • Supported features and interfaces remain unchanged.

lizthegrey and others added 2 commits September 27, 2026 08:58
The honeycombio/avo fork adds a PCALIGN pseudo-instruction that the arm64
lowering passes through. The regenerated assembly is byte-identical. The
newer avo requires newer golang.org/x modules, which require go 1.25.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Place PCALIGN $64 before the 4X and 1X inner loops, which also raises
each function's alignment to 64. Without it the loop heads land wherever
the linker places the function, and the same code measured about ±2%
apart between builds that differed only in what came before it. The
padding runs once per outer iteration.

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

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 240c9290-d5f9-4e6b-9194-25a78310f2eb

📥 Commits

Reviewing files that changed from the base of the PR and between 7b131b4 and 48b49bf.

⛔ Files ignored due to path filters (1)
  • huff0/_generate/go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • huff0/_generate/go.mod

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

Generated and AMD64 Huff0 decompression inner loops now use 64-byte alignment. ARM64 loops cap batch counts by available input and output capacity.

Changes

Huff0 decompression loops

Layer / File(s) Summary
Align decompression inner loops
huff0/_generate/gen.go, huff0/_generate/go.mod, huff0/decompress_amd64.s
The Decompress4X and Decompress1X generators emit 64-byte alignment, and AMD64 routines align their inner loops. The generator module’s Go version and dependency versions also change.
Cap ARM64 batch counts
huff0/decompress_arm64.s
Four-stream and one-stream loops cap batch counts by available input and output capacity. They exit when the resulting count is zero and calculate output endpoints with 5-, 7-, or 14-byte strides.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Refactor

Merge Risk: ⚪ Minimal · up to 48b49

The reviewed changes add loop alignment without an identified behavioral regression. The PR appears mergeable after normal checks.

Architecture Summary

Architecture risk: 🔵 Low · up to 48b49

The change affects 1 system.

Changed systems: huff0

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — huff0 (service) was modified; 4 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in huff0/_generate/gen.go: The Decompress4X generator emits 64-byte alignment before inner_loop, so each batch reaches the loop head at an aligned address.
  • observed — Modified behavior in huff0/_generate/gen.go: The Decompress1X generator emits 64-byte alignment before inner_loop, so each batch reaches the loop head at an aligned address.
  • observed — Modified behavior in huff0/decompress_amd64.s: Adds 64-byte alignment before the inner loop in decompress4x_main_loop_amd64.
  • observed — Modified behavior in huff0/decompress_amd64.s: Adds 64-byte alignment before the inner loop in decompress4x_8b_main_loop_amd64.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: aligning the Huffman decode inner loops to 64-byte boundaries.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lizthegrey

Copy link
Copy Markdown
Contributor Author

The same alignment for the zstd sequence decode/execute loops is staged on top of #1212: honeycombio/compress@lizf.zstd-twopass-prefetch...lizf.zstd-pcalign-on-1212. It touches the same generator lines as #1212, so I'll open it once #1212 is merged.

While staging it I hit a Go assembler bug, in the case you reported in golang/go#74648: a label directly on a PCALIGN. #1212's prefetch priming block ends with a skip label that falls exactly on the loop head. On Go 1.26 and newer, cmd/asm for amd64 then loops forever whenever it has to widen a branch in that function. The fix for #74648 (CL 696915) processes forward jumps to a PCALIGN but still skips resetting p.Rel for it, so the jump list becomes cyclic on the second layout pass. On Go 1.25 and older the same code assembles, but the jump goes to the wrong place (#74648 itself).

The staged zstd change therefore emits the PCALIGN before that label rather than after it. None of the 18 sites in this PR has a label directly before it.

The honeycombio/avo fork now rejects a label placed directly on a
PCALIGN, which the amd64 assembler mishandles (golang/go#74648,
golang/go#81792). The regenerated assembly is byte-identical.

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

@klauspost klauspost left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge alignment. OK for the inner loop, though. Wondering if we should insert a JMP to the aligned part, so the CPU can skip the alignment code?

@lizthegrey

Copy link
Copy Markdown
Contributor Author

I don't think a JMP would help here. The padding runs rarely, and it's cheaper to run than a jump would be.

  • When it runs: the NOPs sit only on the fall-through path from the outer loop into inner_loop. The inner loop's back-edge jumps straight to the aligned head, so they run once per outer-loop pass, and each pass decodes a batch of many iterations. They never run per symbol.
  • What it is: in the built binaries it's 4–5 multi-byte NOPs (34–39 bytes) on amd64 and 7 NOOPs (28 bytes) on arm64. On a 4–6-wide core that retires in about a cycle per pass.
  • Why a JMP isn't better: a taken jump costs at least a fetch redirect, and more if it isn't in the branch target buffer. It also uses a predictor entry and adds bytes of its own. It would only pay off with much longer padding than this, and with a fall-through path that runs far more often.

On "huge": $64 puts each loop head at the start of a cache line. That's the fetch unit for the arm64 cores, while x86's uop cache works in 32-byte windows. If you'd rather have less padding, the generator can emit $32 on amd64 only; it's a one-line change, but I haven't measured $32 against $64 for these loops.

@klauspost

Copy link
Copy Markdown
Owner

It is fine. Just wondering. Some day I may do actual tests.

I don't think your AI's analysis is more than speculation. Instruction decoding will be less with a JMP - and of course it is fully predictable. Anyway, doesn't matter, but up to 63 bytes of padding will be quite a bit.

@klauspost
klauspost merged commit 0533a67 into klauspost:master Sep 28, 2026
29 checks passed
@lizthegrey
lizthegrey deleted the lizf.huff0-pcalign branch September 28, 2026 17:55
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.

2 participants