Skip to content

Send small static files with the headers and large bodies without a copy - #2589

Merged
yhirose merged 1 commit into
masterfrom
coalesce-small-body
Sep 27, 2026
Merged

yhirose merged 1 commit into
masterfrom
coalesce-small-body

Conversation

@yhirose

@yhirose yhirose commented Sep 27, 2026

Copy link
Copy Markdown
Owner

This takes the two gains measured on #2541 with a change confined to Server::write_response_core(), without adding public API or platform-specific send code.

  • A small file served from a mount point or through set_file_content() (under CPPHTTPLIB_SEND_BUFSIZ) is read into the header buffer, so the status line, the headers and the body leave in a single write. Before, the body came from a content provider and always went out in a second write.
  • A large set_content() body (CPPHTTPLIB_SEND_BUFSIZ or more) is written directly after the headers instead of being copied into the header buffer first.

Only file-backed providers are coalesced. A user-supplied provider may produce its data over time, and holding the headers back until it finishes would stall the client (ErrorHandlingTest.StreamReadTimeout covers this). A private Response::is_file_content_provider_ flag marks the file case; every provider setter resets it.

benchmark/ab.sh, 7 alternating rounds, ratio of medians against master:

workload plain TLS
/static/small.js 1.237x (p = 0.001) 1.245x (p = 0.001)
/large (1 MiB) 1.248x (p = 0.001) 1.108x (p = 0.001)
/static/large.bin (1 MiB) 1.003x (ns) 1.006x (ns)
/ 0.996x (ns)

Against #2541 itself the only separated differences were /static/large.bin plain (0.954x), where #2541 sends the headers and a large file in one sendmsg(), and /static/small.js TLS (0.983x).

Thanks to @gsmecher for the idea and the analysis in #2541.

A file served from a mount point or through set_file_content() left in two
writes, one for the status line and headers and one for the body, because the
body came from a content provider. A small file is now read into the header
buffer so the whole response leaves in a single write. Only file-backed
providers are coalesced this way: a user-supplied provider may produce its data
over time, and holding the headers back until it finishes would stall the
client.

A large set_content() body was copied into the header buffer before being
sent. A body of CPPHTTPLIB_SEND_BUFSIZ or more is now written directly after
the headers, which saves the copy at the cost of one extra write.
@yhirose
yhirose merged commit 2fb2dbb into master Sep 27, 2026
44 of 45 checks passed
@yhirose
yhirose deleted the coalesce-small-body branch September 27, 2026 02:21
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