Skip to content

Write straight into the writer buffer, with a noalias writer - #4

Merged
lalinsky merged 2 commits into
mainfrom
perf-writer-helpers
Oct 16, 2026
Merged

lalinsky merged 2 commits into
mainfrom
perf-writer-helpers

Conversation

@lalinsky

@lalinsky lalinsky commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

The writer changes msgpack.zig got in #50 and #51, plus two more from zt (#41, #44).

Write straight into the buffer, with a noalias writer

Writer.writeAll loops over write, so every key and every run of string bytes went through an out-of-line memcpy, even for a few bytes. On twitter that was a third of the encode profile. writeByte and writeAll now copy into the buffer directly when it has room, and leave the drain to a cold out-of-line function.

They take the writer as noalias. Without it, a byte stored into the buffer may, as far as the compiler knows, have changed the writer's own fields, so end and the buffer are reloaded after every write.

Integers in place, short strings inline

  • writeInt formatted into a temporary buffer and copied it out, a variable-length memcpy call per number. When the buffer has room for the longest value of the type, the digits are now written in place.
  • Strings under 16 bytes never reach the vector loop but still paid for a call to writeString. They're escaped inline with a lookup table now, and the vector scan stays out of line for longer strings.
  • The string, integer, float and escape writers take the writer as noalias too.

Numbers

Median ns per encode, ReleaseFast, i7-6700, the same harness built against each version, 5 interleaved runs:

before writer helpers + ints, short strings
twitter (478 KB, string-heavy) 196,657 145,224 138,658 (-29.5% total)
canada (2 MB, floats) 4,964,616 4,777,284 4,744,561 (-4.4%)
small (222 B message) 216 186 171 (-20.8%)

Instructions per encode drop at each step as well, so it isn't code layout moving around. That's worth checking on this machine, because it is a Skylake with the JCC erratum, where an unrelated change can move a loop by ±30%.

On canada, 82% of the time is std.fmt.float.render, which none of this touches.

zig build test passes on 0.16 and 0.17. Encoding twitter, canada and small round-trips to the same document.

The string loop here keeps the old @bitCast mask, so it will conflict with #6 on one line. Whichever lands second takes compat.vectorMask.

@lalinsky
lalinsky added this pull request to stack #5 October 16, 2026 20:04
Base automatically changed from bench-fixes to main October 16, 2026 20:11
Writer.writeAll loops over write, so every key and every string run
went through an out-of-line memcpy, even for a few bytes. writeByte and
writeAll now copy into the buffer directly when it has room, and leave
the drain to a cold out-of-line function.

Both take the writer as noalias. Without it, a byte stored into the
buffer may have changed the writer's own fields, so end and the buffer
are reloaded after every write.

Median ns per encode, ReleaseFast, i7-6700:

  twitter  197309 -> 145861  (-26%)
  canada  4998434 -> 4796230  (-4%, std's float formatting dominates)
  small       218 ->     187  (-14%)
@lalinsky
lalinsky force-pushed the perf-writer-helpers branch from ab5552d to 524c652 Compare October 16, 2026 20:11
Both from zt (#41, #44):

writeInt formatted into a temporary buffer and copied that out, a memcpy
call of variable length for every number. When the buffer has room for
the longest value of the type, count the digits and write them in place.

Strings shorter than 16 bytes never reach the vector loop, but still paid
for a call to writeString, which set up the vector constants and sent the
writer's state through memory. Escape them inline with a lookup table and
keep the vector scan out of line for longer strings.

The string, integer, float and escape writers take the writer as noalias
too, like writeAll.

Median ns per encode, ReleaseFast, i7-6700, against the previous commit:

  twitter  145224 -> 138658  (-4.5%)
  canada  4777284 -> 4744561  (-0.7%)
  small       186 ->     171  (-8.3%)

Instructions per encode drop by about 5% on twitter and 4% on small, so
this isn't code layout moving around.
@lalinsky
lalinsky merged commit 35f2629 into main Oct 16, 2026
4 checks passed
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