Skip to content

perf(packet): wire SIMD Internet checksum for long payloads - #492

Open
dyxushuai wants to merge 3 commits into
arcboxlabs:masterfrom
dyxushuai:perf/packet-checksum-simd
Open

perf(packet): wire SIMD Internet checksum for long payloads#492
dyxushuai wants to merge 3 commits into
arcboxlabs:masterfrom
dyxushuai:perf/packet-checksum-simd

Conversation

@dyxushuai

@dyxushuai dyxushuai commented Jul 22, 2026

Copy link
Copy Markdown

Why

Long checksum buffers still paid the scalar accumulation cost. The original dispatch also sent 64-byte buffers through SIMD even though that path was slower on x86_64, and the AArch64 NEON accumulator serialized every chunk for long-buffer workloads.

How

  • Route only buffers longer than 64 bytes through the existing NEON/SSSE3 path.
  • Use four independent NEON accumulators before the final reduction.
  • Keep big-endian AArch64 on the scalar fallback because the NEON byte swap assumes little-endian input.
  • Remove the unused checksum_simd_neon compatibility shim.
  • Add boundary, odd-tail, and length-sweep correctness coverage plus a dispatch-aware benchmark.

Tests

  • cargo test -p arcbox-packet --all-targets
  • cargo clippy -p arcbox-packet --all-targets -- -D warnings
  • cargo clippy -p arcbox-packet --target aarch64-unknown-linux-gnu --all-targets -- -D warnings
  • cargo run -p arcbox-packet --example checksum_bench --release

The x86_64 benchmark keeps 64-byte inputs on the scalar path; 1500-byte and 64 KiB inputs remain about 2x faster than scalar in the local release run.

Route checksum_add through the existing NEON/SSSE3 accumulator for
buffers of 64 bytes or more; short headers stay scalar. Add a small
microbench example for scalar vs SIMD throughput.
Copilot AI lite review requested due to automatic review settings July 22, 2026 07:34
Mark the legacy NEON entry #[deprecated] and use consistent expression-style
cfg branches in checksum_add_fast.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR speeds up ArcBox’s Internet-checksum hot path by wiring the existing NEON/SSSE3 implementations into checksum_add for long buffers, improving TCP/UDP full-segment checksum throughput while keeping short headers on the scalar loop to avoid SIMD setup overhead.

Changes:

  • Add a length threshold and route checksum_add to a new “fast path” that selects NEON on AArch64 and SSSE3 on x86_64 (with scalar fallback).
  • Refactor the SIMD routines to return the raw ones’-complement sum (u32) and keep folding/complementing centralized in checksum_fold.
  • Add a microbench example plus additional long-buffer correctness tests to guard the new dispatch behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
common/arcbox-packet/src/checksum.rs Adds SIMD dispatch for long buffers, refactors SIMD helpers to return sums, and extends tests to cover long-payload correctness.
common/arcbox-packet/examples/checksum_bench.rs Adds a small throughput microbench comparing scalar vs SIMD checksum computation.

Comment thread common/arcbox-packet/src/checksum.rs Outdated
Copilot AI review requested due to automatic review settings July 22, 2026 07:38

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — two minor points inline.

Reviewed changes — wires the already-existing NEON/SSSE3 checksum kernels into the common checksum_add for payloads >= 64 bytes, so TCP/UDP full-segment checksums use SIMD on large buffers.

  • SIMD dispatch in checksum_add — buffers >= SIMD_THRESHOLD (64 B) route to checksum_add_fast; shorter stay scalar. This is the hot path behind checksum, tcp_checksum, udp_checksum.
  • Kernels return raw u32 sumchecksum_add_neon / checksum_add_ssse3 now return the un-folded ones'-complement sum so partial sums compose with the pseudo-header before one final checksum_fold; switched to slice::as_chunks::<16>().
  • API tidychecksum_simd kept as a pub test/bench entry, checksum_simd_neon retained as a pub-unsafe shim, and the old comment noise stripped.
  • Tests + bench — added long-buffer scalar/SIMD equivalence tests (4096 B and a 1500 B TCP segment) and a checksum_bench example.

Correctness checks out: scalar and SIMD both compute the true arithmetic word sum, and the u32 accumulators don't wrap below ~512 KB (NEON) / ~1 MB (SSSE3) — far above any packet — so the final fold is identical. The added tests pin this down.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread common/arcbox-packet/src/checksum.rs Outdated
Comment thread common/arcbox-packet/src/checksum.rs Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes — this run covers commit 82b17fe, which addresses both minor points from the prior review.

  • checksum_add_fast aarch64 arm — the cfg-gated return was dropped in favor of a bare block tail expression, removing the clippy::needless_return risk on the P0 aarch64 target.
  • checksum_simd_neon — kept for backward compat but now carries an explicit #[deprecated] attribute and a doc pointer to checksum / checksum_simd, so the retained shim is intentional rather than stray dead code.

Both resolutions are clean and introduce no new behavior — the SIMD dispatch and correctness properties reviewed previously are unchanged.

Pullfrog  | Fix it ➔View workflow run | Using Claude Opus𝕏

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread common/arcbox-packet/src/checksum.rs
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

The PR routes checksum accumulation for long buffers through architecture-specific SIMD implementations while preserving scalar fallbacks.

  • Adds runtime SSSE3 dispatch on x86_64 and architecture/endian-aware NEON dispatch on AArch64.
  • Refactors SIMD routines to return an unfolded sum shared with existing checksum folding.
  • Adds scalar/SIMD equivalence tests, long TCP-segment coverage, and a checksum microbenchmark.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains within the eligible follow-up-review scope.

Important Files Changed

Filename Overview
common/arcbox-packet/src/checksum.rs Adds long-buffer SIMD dispatch, architecture-safe fallbacks, refactored accumulation routines, and expanded correctness tests.
common/arcbox-packet/examples/checksum_bench.rs Adds a standalone microbenchmark comparing dispatched, scalar, and SIMD checksum throughput.

Reviews (2): Last reviewed commit: "fix(packet): avoid checksum SIMD regress..." | Re-trigger Greptile

@PeronGH PeronGH self-assigned this Jul 31, 2026

@PeronGH PeronGH left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this — nice catch. checksum_add really was leaving the existing NEON/SSSE3 code unreachable, and the refactor to have the SIMD paths return the unfolded sum (rather than a folded u16) is the right shape: it composes cleanly with the pseudo-header accumulation in tcp_checksum/udp_checksum, which the old fold-inside-SIMD API couldn't. The example bench is a welcome addition too.

One thing worth measuring before this lands: our P0 target is macOS Apple Silicon, and the numbers look different there. On an M-series host I get (release, 3 runs, very reproducible):

len scalar this PR (NEON)
64 B 89 Gbit/s 130 1.5x
1500 B 127 166 1.3x
9000 B 150 115 0.77x
16 KiB 171 112 0.66x
64 KiB 190 124 0.65x

So it's a win at MTU size but a regression above ~4 KiB. That matters because the hottest consumer is finalize_virtio_net_checksum (virt/arcbox-vmm/src/device/checksum.rs), and we advertise VIRTIO_NET_F_CSUM unconditionally alongside GUEST_TSO4/6 — so guest TX hands us GSO segments up to 64 KiB, which is exactly the range that regresses.

The cause looks like the accumulator rather than the idea: checksum_add_neon chains every vpadalq_u16 onto the previous one, so the loop is latency-bound instead of throughput-bound, while LLVM already auto-vectorizes the scalar loop pretty well on aarch64 (scalar throughput climbs with length, 89 -> 190 Gbit/s, which is the tell). I tried a variant with several independent accumulators summed at the end and it went to ~750 Gbit/s at 64 KiB — 2.7x over scalar, byte-identical results — so there's a lot on the table here. The SSSE3 path has the same shape with 2 accumulators, so it may have headroom as well.

Smaller notes:

  • cargo clippy -p arcbox-packet warns missing_safety_doc on checksum_simd_neon, and we gate on zero warnings. Since rg finds no callers of it anywhere in the workspace and the crate is unpublished, I'd suggest just deleting it rather than deprecating — breaking changes are explicitly fine here (see CLAUDE.md).
  • SIMD_THRESHOLD = 64 with >= means a 64 B buffer does take the SIMD path, so the x86 0.9x at 64 B in your table applies rather than being avoided. Might be worth 65, or measuring where the crossover actually sits.
  • test_checksum_add_fast_matches_scalar_long uses 4096 B, an exact multiple of 16, so the SIMD remainder/odd-byte tail isn't covered (test_checksum_simd at 100 B leaves an even 4-byte remainder). I swept lengths 0..=600 against scalar locally and everything agrees — the code is correct — but a small length-sweep test would lock that in, since the tail is where checksum bugs like to hide.

Happy to help dig into any of this if useful.

Copilot AI review requested due to automatic review settings August 5, 2026 03:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

common/arcbox-packet/src/checksum.rs:18

  • The SIMD_THRESHOLD docs read as if SIMD is used at 64 bytes, but checksum_add only routes to the fast path when len > SIMD_THRESHOLD (65+). Please adjust the wording so the boundary is unambiguous.
/// Length at which SIMD `checksum_add` is used. Short headers stay scalar
/// so setup cost does not dominate.
const SIMD_THRESHOLD: usize = 64;

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

The correctness work in d533def is solid, but the Apple Silicon regression this commit exists to fix is still unmeasured.

Reviewed changes — this run covers d533def, which responds to the assignee's benchmark review and to the >= threshold thread.

  • Threshold flipped to strict > — a 64 B buffer now stays on the scalar loop, and the bench sweeps 63/64/65 to pin the boundary.
  • NEON kernel re-shaped into four independent accumulatorsas_chunks::<16>() then as_chunks::<4>(), with group_remainder folded into sum0 and a vaddq_u32 tree reduction, replacing the single serialized vpadalq_u16 chain.
  • Big-endian AArch64 routed to scalarchecksum_add_fast gained a target_endian = "big" arm, since vrev16q_u8 assumes a little-endian host.
  • Deleted the checksum_simd_neon shim — no in-repo callers, and checksum_simd collapsed to a single non-cfg'd fold(checksum_add_fast(..)).
  • Added a 0..=600 length sweeptest_checksum_paths_match_scalar checks checksum_add, checksum_add_fast, and checksum_simd against scalar at every length, which covers the group/group_remainder/odd-tail boundaries the old 4096 B test missed.

I worked the NEON arithmetic through independently and it is identical to scalar at every length: vrev16q_u8 + vreinterpretq_u16_u8 reproduces u16::from_be_bytes, the odd-byte tail matches, and as_chunks cannot mis-slice. Accumulator overflow is bounded by the final vaddvq_u32, which wraps only above ~128 KiB of input — unchanged from the single-accumulator version, and 2x above the 64 KiB maximum checksummed span. CI runs on macos-26, so the new length sweep does exercise the NEON path.

⚠️ The P0 regression this commit targets has no Apple Silicon measurement

The assignee measured the previous NEON path at 0.77x / 0.66x / 0.65x versus scalar at 9 KiB / 16 KiB / 64 KiB on an M-series host. That is precisely the size range that reaches finalize_virtio_net_checksum once GUEST_TSO4/6 is negotiated, and macOS Apple Silicon is the P0 platform. The four-accumulator rewrite is the right shape for that diagnosis, but the PR's Tests section reports only an x86_64 benchmark run plus an aarch64 clippy cross-check — nothing that would show whether the fix landed. If it under-delivers, this merges a ~35% regression on the hottest datapath in the tree.

Technical details
# Apple Silicon benchmark evidence is missing for a P0 perf change

## Affected sites
- `common/arcbox-packet/src/checksum.rs:211``checksum_add_neon`, the kernel the
  four-accumulator rewrite targets. Only reachable on aarch64.
- `common/arcbox-packet/src/checksum.rs:27``checksum_add` dispatch; every caller
  of `checksum` / `tcp_checksum` / `udp_checksum` routes through here above 64 B.
- Consumer chain: `virt/arcbox-vmm/src/device/checksum.rs::finalize_virtio_net_checksum`
  -> `arcbox_net::nat_engine::checksum::{tcp_checksum, udp_checksum}`
  -> `arcbox_packet::checksum::checksum_add`.

## Required outcome
- An M-series run of `cargo run -p arcbox-packet --example checksum_bench --release`
  posted on the PR, showing the `checksum` / `scalar` / `simd` rows at 9000, 16384
  and 65536 bytes.
- The dispatched (`checksum`) row must be at least at parity with `scalar` across
  that whole range, not just at 1500 B. If it is not, the threshold needs an upper
  bound, or the kernel needs more work, before this lands.

## Open questions for the human
- `checksum_add_ssse3` still carries two serialized `_mm_add_epi32` accumulators.
  Is leaving x86_64 alone a deliberate deferral (P2 platform) or an oversight?

ℹ️ Nitpicks

  • common/arcbox-packet/src/checksum.rs:16 — see the inline comment; the SIMD_THRESHOLD doc line is now off by one relative to the strict > dispatch.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment on lines +16 to +18
/// Length at which SIMD `checksum_add` is used. Short headers stay scalar
/// so setup cost does not dominate.
const SIMD_THRESHOLD: usize = 64;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: with the dispatch now on strict >, 64 is the longest length that stays scalar, so "length at which SIMD is used" reads one off. The checksum_add doc above already says "longer than"; worth matching here.

Suggested change
/// Length at which SIMD `checksum_add` is used. Short headers stay scalar
/// so setup cost does not dominate.
const SIMD_THRESHOLD: usize = 64;
/// Longest buffer that stays on the scalar loop — `checksum_add` uses SIMD
/// only above this length, so setup cost does not dominate short headers.
const SIMD_THRESHOLD: usize = 64;

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.

3 participants