aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-27 18:49:22 +0100
committereunhhu <[email protected]>2026-09-27 18:49:22 +0100
commitb469bdaee395a4c49b23d6b3a146ea31ef7c0959 (patch)
tree4daf45223e1170aec3642901666443a8f9861c3a
parent0d0f1fcdfc3b50dfc43c415607707174271f4835 (diff)
downloadpure-b469bdaee395a4c49b23d6b3a146ea31ef7c0959.tar.gz
pure-b469bdaee395a4c49b23d6b3a146ea31ef7c0959.zip
Write an answer's head without printf
Three snprintf calls a response (the head, its ETag and a delivery's batch size) spent a sixth of a small read's user time in printf's format scanning. The head is now written part by part, the same bytes as before, clipped at the end of its buffer where snprintf's would-be length could have sent bytes past it.
-rw-r--r--serve/serve.c54
1 files changed, 45 insertions, 9 deletions
diff --git a/serve/serve.c b/serve/serve.c
index 622b3ae..14b0c45 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -288,23 +288,59 @@ static void close_connection(Connection *c) {
--host->active;
}
+/* An answer's head is written in place, part by part: printf's scanning of
+ * its format took a sixth of a small read's time. Clipped at the end of the
+ * buffer, which its parts, all short, never reach. */
+typedef struct { char *at, *end; } Head;
+
+static void put_text(Head *h, const char *text) {
+ size_t length = strlen(text), room = (size_t)(h->end - h->at);
+ if (length > room) length = room;
+ memcpy(h->at, text, length);
+ h->at += length;
+}
+
+static void put_decimal(Head *h, uint64_t value) {
+ char digits[20];
+ size_t count = 0;
+ do digits[count++] = (char)('0' + value % 10); while (value /= 10);
+ while (count && h->at < h->end) *h->at++ = digits[--count];
+}
+
/* Answer with a body of `count` extents (`size` bytes in all); the ticket
* keeps them alive. One writev sends as much as the socket takes. */
static void respond_typed(Connection *c, int status, const char *reason, const char *error, int64_t revision,
const char *type, const struct iovec *extents, size_t count, size_t size) {
- char tag[48] = "";
- if (revision > 0) snprintf(tag, sizeof(tag), "ETag: \"%" PRId64 "\"\r\n", revision);
+ Head h = {c->head, c->head + sizeof(c->head)};
+ put_text(&h, "HTTP/1.1 ");
+ put_decimal(&h, (uint64_t)status);
+ put_text(&h, " ");
+ put_text(&h, reason);
+ put_text(&h, "\r\nContent-Length: ");
+ put_decimal(&h, size);
+ put_text(&h, c->keep_alive ? "\r\n" : "\r\nConnection: close\r\n");
+ put_text(&h, "Content-Type: ");
+ put_text(&h, type);
+ put_text(&h, "\r\nCache-Control: no-store\r\nX-Pure-Error: ");
+ put_text(&h, error);
+ put_text(&h, "\r\n");
+ if (revision > 0) {
+ put_text(&h, "ETag: \"");
+ put_decimal(&h, (uint64_t)revision);
+ put_text(&h, "\"\r\n");
+ }
/* A delivery's answer tells how many numbered messages one request may carry. */
- char batch[32] = "";
- if (*c->source) snprintf(batch, sizeof(batch), "X-Pure-Batch: %d\r\n", BATCH);
- int length = snprintf(c->head, sizeof(c->head),
- "HTTP/1.1 %d %s\r\nContent-Length: %zu\r\n%sContent-Type: %s\r\n"
- "Cache-Control: no-store\r\nX-Pure-Error: %s\r\n%s%s\r\n",
- status, reason, size, c->keep_alive ? "" : "Connection: close\r\n", type, error, tag, batch);
+ if (*c->source) {
+ put_text(&h, "X-Pure-Batch: ");
+ put_decimal(&h, BATCH);
+ put_text(&h, "\r\n");
+ }
+ put_text(&h, "\r\n");
+ size_t length = (size_t)(h.at - c->head);
c->out = count < sizeof(c->small) / sizeof(*c->small) ? c->small : malloc((count + 1) * sizeof(*c->out));
c->phase = WRITING;
if (!c->out) { c->keep_alive = false; c->out = c->small; count = 0; } /* cannot send it: the close tells */
- c->out[0] = (struct iovec){c->head, (size_t)length};
+ c->out[0] = (struct iovec){c->head, length};
if (count) memcpy(c->out + 1, extents, count * sizeof(*extents));
c->out_count = count + 1;
c->out_at = 0;