From eb37aad3ae7f9df1d6c0b09108e3a2d69d0d4d82 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 07:30:51 +0100 Subject: Read JSON bodies in place, groups included (082) pure-serve no longer copies a JSON body's strings into the message's payload. The ingress is the prefix, a table of each field's start and size, and the body as it came; the connection thread scans the body once, puts numbers into the prefix and records where each string is. Only a string with escapes is unescaped, over itself (it never grows). A group of fields comes as an object inside the body and, for a fixed field, as its path in the query (?to.zip=7). A group where a field belongs, a value where a group does, an array or true/false/null is json_type; an unknown member, inside a group too, json_field. The string scan and UTF-8 checks now pass over eight bytes at a time while none needs a look (pure_utf8_step, so every text check gains). On the Pi, posting {"name":"k","text":"x..."} to a stateless app went from 652-655 to 3,737-3,953 requests/s with 256 KiB of text (server CPU 1.52 to 0.25 ms a request, peak RSS 5.2 to 3.2-3.4 MiB) and from 29.1k to 76.3-76.7k/s with 4 KiB; the load generator gains a json method and benchmarks/http/echo.aup is the app. --- benchmarks/http/README.md | 3 +++ benchmarks/http/echo.aup | 5 +++++ benchmarks/http/load.c | 33 +++++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 benchmarks/http/echo.aup (limited to 'benchmarks') diff --git a/benchmarks/http/README.md b/benchmarks/http/README.md index 3672c08..af9f62d 100644 --- a/benchmarks/http/README.md +++ b/benchmarks/http/README.md @@ -32,6 +32,9 @@ python3 run.py --module scores.pmod --suite app --rounds 3 --seconds 4 \ --only pure-serve,pure-serve-2,axum-memory,axum-durable,drogon-memory,drogon-durable ``` +JSON 본문의 비용은 상태 없는 앱 [echo.aup](echo.aup)에 `load PORT 연결 초 json 1 keep-alive 본문바이트`로 잰다 +(`{"name":"k","text":"x…"}`를 `POST /inputs/echo`로, [082](../../docs/research/082-json-in-place.md)). + 앱 벤치마크의 마지막 작업(`mixed`)은 내구성 점수 POST 8연결과 합계 GET 56연결을 부하 생성기 둘로 함께 보내, 쓰기가 동기화하는 동안 읽기가 기다리는지 본다([080](../../docs/research/080-snapshot-reads.md)). 두 행(posts, reads)으로 나오고, CPU 1초당 답은 두 행을 합친 것이다. diff --git a/benchmarks/http/echo.aup b/benchmarks/http/echo.aup new file mode 100644 index 0000000..cac3e04 --- /dev/null +++ b/benchmarks/http/echo.aup @@ -0,0 +1,5 @@ +input echo = message { + name: text + text: text +} +on echo { reply echo.name } diff --git a/benchmarks/http/load.c b/benchmarks/http/load.c index fe50deb..d07b2bb 100644 --- a/benchmarks/http/load.c +++ b/benchmarks/http/load.c @@ -2,7 +2,7 @@ /* HTTP/1.1 load for Pure's benchmarks: N connections on one epoll thread, * each sending one request at a time and measuring its latency. * - * load PORT CONNECTIONS SECONDS get|put|score|total|page|note KEYS [keep-alive|close] [BODY] + * load PORT CONNECTIONS SECONDS get|put|score|total|page|note|json KEYS [keep-alive|close] [BODY] * get: GET /objects/kI for I in [0, KEYS). * put: connection C edits its own key kC (C mod KEYS), inserting BODY bytes * at 0 with If-Match on the revision it last saw (If-None-Match: * at @@ -10,7 +10,8 @@ * The scoreboard app: score posts a point for kC (POST /inputs/score?points=1, * the name as the body), total reads GET /views/total and page GET * /views/leaders?from=0&count=20. note reads GET /views/page (a text state - * shown as it is, as in a notes app). + * shown as it is, as in a notes app). json posts {"name":"k","text":"x..."} + * (BODY bytes of text) to POST /inputs/echo as application/json. * Prints one JSON line: requests/s, statuses, latency percentiles (µs). */ #include #include @@ -68,7 +69,7 @@ static uint64_t percentile(uint64_t total, double p) { } static int port, connections_count, keys; -static enum { GET, PUT, SCORE, TOTAL, PAGE, NOTE } method; +static enum { GET, PUT, SCORE, TOTAL, PAGE, NOTE, JSON } method; static bool put, keep_alive; static char *body; static size_t body_bytes; @@ -76,6 +77,15 @@ static int epoll_fd; static void prepare(Connection *c) { const char *closing = keep_alive ? "" : "Connection: close\r\n"; + if (method == JSON) { + c->request_size = (size_t)snprintf(c->request, sizeof(c->request), + "POST /inputs/echo HTTP/1.1\r\nHost: localhost\r\nContent-Type: application/json\r\n" + "Content-Length: %zu\r\n%s\r\n", body_bytes, closing); + c->sent = c->received = c->body_sent = 0; + c->body_size = body_bytes; + c->started = now_us(); + return; + } if (method >= SCORE) { char name[16]; int length = snprintf(name, sizeof(name), "k%d", c->key); @@ -141,21 +151,28 @@ static bool complete(Connection *c, int *status, bool *closing, size_t *length) int main(int argc, char **argv) { if (argc < 6) { - fputs("load PORT CONNECTIONS SECONDS get|put|score|total|page|note KEYS [keep-alive|close] [BODY]\n", stderr); + fputs("load PORT CONNECTIONS SECONDS get|put|score|total|page|note|json KEYS [keep-alive|close] [BODY]\n", stderr); return 2; } port = atoi(argv[1]); connections_count = atoi(argv[2]); double seconds = atof(argv[3]); - static const char *const methods[] = {"get", "put", "score", "total", "page", "note"}; - for (method = GET; method <= NOTE && strcmp(argv[4], methods[method]); ++method) {} - if (method > NOTE) { fprintf(stderr, "load: unknown method %s\n", argv[4]); return 2; } + static const char *const methods[] = {"get", "put", "score", "total", "page", "note", "json"}; + for (method = GET; method <= JSON && strcmp(argv[4], methods[method]); ++method) {} + if (method > JSON) { fprintf(stderr, "load: unknown method %s\n", argv[4]); return 2; } put = method == PUT; keys = atoi(argv[5]); keep_alive = argc > 6 && !strcmp(argv[6], "keep-alive"); body_bytes = argc > 7 ? strtoul(argv[7], NULL, 10) : 16; - body = calloc(body_bytes + 8, 1); + body = calloc(body_bytes + 32, 1); memset(body + 8, 'x', body_bytes); + if (method == JSON) { + static const char open[] = "{\"name\":\"k\",\"text\":\""; + memcpy(body, open, sizeof(open) - 1); + memset(body + sizeof(open) - 1, 'x', body_bytes); + memcpy(body + sizeof(open) - 1 + body_bytes, "\"}", 2); + body_bytes += sizeof(open) - 1 + 2; + } epoll_fd = epoll_create1(0); Connection *all = calloc((size_t)connections_count, sizeof(*all)); for (int i = 0; i < connections_count; ++i) { -- cgit v1.2.3