Trim redundant work from the request hot path - #10
Merged
Merged
Conversation
Profiling with eprof showed the parse path running twice per request: after each response the loop re-parsed the leftover buffer even when it was empty. Skip straight back to recv in that case. The header split also re-scanned the line list for Content-Length; fold that check into the split loop instead. The per-request GC heap check (process_info + threshold) is removed entirely, and responses with binary bodies size the Content-Length with byte_size instead of iolist_size. Together this cuts whitecap's share of profile time from ~25% to ~19% (the rest is socket syscalls). Localhost throughput is unchanged; the bench is syscall-bound.
With the JIT and module-wide type propagation these no longer have a measurable effect: parse-loop and end-to-end benchmarks are within noise with and without them, while the beam is 12% larger and stack traces lose inlined frames.
socket:send flattens iolists with list_to_binary on every call, and whitecap responses are always iolists. Convert them to iovecs and write them with a single writev-style sendv NIF call instead.
{otp, select_read} lands in OTP 28.0, not 27.3 as the workflow
claimed. Drop OTP 27 from the matrix and run dialyzer on every
matrix entry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
eprof under load (64 keepalive connections) showed the parse path running twice per request: after each response the connection loop re-parsed the leftover buffer even when it was empty, costing a full parse attempt and a
binary:spliton an empty binary every time. This skips straight back to recv when the buffer is empty.Also:
process_info(self(), total_heap_size)+ threshold from Invoke garbage collection to avoid dirty gc #8) is removed entirely.byte_sizeinstead ofiolist_size, and empty bodies use a constant<<"0">>.Per-request effect (eprof, before -> after):
whitecap_protocol:request/32 -> 1 calls,binary:split4 -> 3,process_infogone; whitecap's share of profile time drops from ~25% to ~19%, the rest being socket syscalls. Localhost throughput is unchanged (the bench is syscall-bound), so this is a CPU reduction, not a latency win.Stacked on #9.