Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions .github/workflows/bsd-kqueue-benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,6 @@ jobs:
# the mechanism this workload can't reach).
run_bench "CWIST C1M (drain_chunk=8)" "env CWIST_C1M_MODE=1 CWIST_REACTOR_DRAIN_CHUNK=8 /tmp/cwist_server" 9091 "/tmp/cwist_c1m_drainchunk.txt" "/tmp/cwist_c1m_drainchunk_stat.txt" 10

# Late-round mode off (issue #319 experiment): "CWIST C1M" above
# runs with the default CWIST_REACTOR_LATE_US=3000, this leg with
# 0, the pre-experiment behavior. Same binary, one env var - a
# same-runner A/B of the tail (p99.99/p99.999), which cross-run
# comparisons on shared runners are too noisy to show.
run_bench "CWIST C1M (late mode off)" "env CWIST_C1M_MODE=1 CWIST_LATENCY_PROBE=1 CWIST_REACTOR_LATE_US=0 /tmp/cwist_server" 9091 "/tmp/cwist_c1m_lateoff.txt" "/tmp/cwist_c1m_lateoff_stat.txt" 10

# Tuned low-latency profile (CWIST classic only): lower connection
# pressure (c100, 4 wrk threads) so the per-request latency floor is
# visible alongside the c400 throughput numbers.
Expand Down Expand Up @@ -573,8 +566,6 @@ jobs:

cwist_c1m_drainchunk_wrk = parse_wrk("/tmp/cwist_c1m_drainchunk.txt")
cwist_c1m_drainchunk_rss, cwist_c1m_drainchunk_csw = parse_stat("/tmp/cwist_c1m_drainchunk_stat.txt")
cwist_c1m_lateoff_wrk = parse_wrk("/tmp/cwist_c1m_lateoff.txt")
cwist_c1m_lateoff_rss, cwist_c1m_lateoff_csw = parse_stat("/tmp/cwist_c1m_lateoff_stat.txt")

cwist_tuned_wrk = parse_wrk("/tmp/cwist_tuned.txt")

Expand Down Expand Up @@ -607,7 +598,6 @@ jobs:
# cwist_async_defer, so parity with cwist_c1m_* here is the
# expected, desired result, not a null finding.
**server_fields("cwist_c1m_drainchunk", cwist_c1m_drainchunk_wrk, cwist_c1m_drainchunk_rss, cwist_c1m_drainchunk_csw),
**server_fields("cwist_c1m_lateoff", cwist_c1m_lateoff_wrk, cwist_c1m_lateoff_rss, cwist_c1m_lateoff_csw),
**server_fields("cwist_tuned", cwist_tuned_wrk),
**server_fields("axum", axum_wrk, axum_rss, axum_csw),
**server_fields("actix", actix_wrk, actix_rss, actix_csw),
Expand Down Expand Up @@ -644,7 +634,7 @@ jobs:
- name: Copy raw benchmark output
run: |
mkdir -p raw_logs
cp /tmp/cwist.txt /tmp/cwist_stat.txt /tmp/cwist_c1m.txt /tmp/cwist_c1m_stat.txt /tmp/cwist_c1m_arena1.txt /tmp/cwist_c1m_arena1_stat.txt /tmp/cwist_c1m_drainchunk.txt /tmp/cwist_c1m_drainchunk_stat.txt /tmp/cwist_c1m_lateoff.txt /tmp/cwist_c1m_lateoff_stat.txt /tmp/cwist_tuned.txt /tmp/axum.txt /tmp/axum_tuned.txt /tmp/axum_stat.txt /tmp/actix.txt /tmp/actix_stat.txt /tmp/gin.txt /tmp/gin_stat.txt /tmp/spring.txt /tmp/spring_stat.txt /tmp/spring_tuned.txt /tmp/spring_gc.log raw_logs/ 2>/dev/null || true
cp /tmp/cwist.txt /tmp/cwist_stat.txt /tmp/cwist_c1m.txt /tmp/cwist_c1m_stat.txt /tmp/cwist_c1m_arena1.txt /tmp/cwist_c1m_arena1_stat.txt /tmp/cwist_c1m_drainchunk.txt /tmp/cwist_c1m_drainchunk_stat.txt /tmp/cwist_tuned.txt /tmp/axum.txt /tmp/axum_tuned.txt /tmp/axum_stat.txt /tmp/actix.txt /tmp/actix_stat.txt /tmp/gin.txt /tmp/gin_stat.txt /tmp/spring.txt /tmp/spring_stat.txt /tmp/spring_tuned.txt /tmp/spring_gc.log raw_logs/ 2>/dev/null || true
- uses: actions/upload-artifact@v4
with:
name: webserver-result
Expand Down
4 changes: 0 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -896,7 +896,6 @@ TEST_TARGETS = test_worker_affinity \
test_reactor_timer \
test_rx_epoll \
test_reactor_drain_chunk \
test_reactor_late \
test_latency_probe \
test_sstring \
test_abi_layout \
Expand Down Expand Up @@ -1120,9 +1119,6 @@ test_rx_epoll: tests/test_rx_epoll.c src/sys/io/reactor.c src/sys/io/reactor_rx.
# full before/after latency measurement.
test_reactor_drain_chunk: tests/test_reactor_drain_chunk.c src/sys/io/reactor.c
$(CC) $(CFLAGS) -o $@ tests/test_reactor_drain_chunk.c -pthread

test_reactor_late: tests/test_reactor_late.c src/sys/io/reactor.c
$(CC) $(CFLAGS) -o $@ tests/test_reactor_late.c -pthread
./$@

# Latency probe test (issue #166): run twice per build, with the probe
Expand Down
1 change: 0 additions & 1 deletion docs/ko/runtime-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
| `CWIST_REACTOR_DRAIN_CHUNK` | tuning | 0 이상 정수 | `8` (`CWIST_PROFILE`이 설정, 없으면 `64`) | 게시된 작업을 비우는 사이에 처리하는 completion 수. [협력적 큐잉](cooperative-queuing.md) 참고. |
| `CWIST_REACTOR_ROUND_BUDGET_US` | tuning | 마이크로초, 0 이상 | `2000` | reactor 라운드가 이 시간을 넘기면, 대기 중인 re-arm을 라운드 끝이 아니라 콜백 64개마다 커널에 넘깁니다. 라운드는 끝까지 실행됩니다. `0`이면 한도를 끕니다. |
| `CWIST_REACTOR_CQ_GRACE_NS` | tuning | 나노초, 0 이상 | `20000` | 커널에서 잠들기 전에 completion ring을 확인하며 도는 시간. `0`이면 돌지 않습니다. |
| `CWIST_REACTOR_LATE_US` | tuning | 마이크로초, 0 이상 | `3000` | 실험 기능(#319). reactor 라운드가 completion을 수확한 뒤 이 시간을 넘기면, 남은 콜백을 지연 모드로 처리합니다: 콜백마다 re-arm을 바로 커널에 넘기고, 게시된 작업을 콜백마다 비우며, 파이프라인 HTTP/1.1 연결은 양보하지 않고 쌓인 요청을 처리합니다. io_uring 전용. `0`이면 끕니다. |

## 진단

Expand Down
1 change: 0 additions & 1 deletion docs/runtime-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ Each variable has one status. The status says what v4 promises about it.
| `CWIST_REACTOR_DRAIN_CHUNK` | tuning | integer ≥ 0 | `8` (set by `CWIST_PROFILE`; `64` without it) | Completions handled between drains of posted work. See [cooperative queuing](cooperative-queuing.md). |
| `CWIST_REACTOR_ROUND_BUDGET_US` | tuning | microseconds ≥ 0 | `2000` | Once a reactor round runs longer than this, parked re-arms are flushed to the kernel every 64 callbacks instead of at the end of the round. The round still runs to completion. `0` disables the bound. |
| `CWIST_REACTOR_CQ_GRACE_NS` | tuning | nanoseconds ≥ 0 | `20000` | How long the reactor spins on the completion ring before it sleeps in the kernel. `0` disables the spin. |
| `CWIST_REACTOR_LATE_US` | tuning | microseconds ≥ 0 | `3000` | Experiment (#319). Once a reactor round has run this long since its completions were reaped, the rest of the round runs in late mode: each callback's re-arm is submitted right after it, posted work is drained after each callback, and a pipelined HTTP/1.1 connection serves its buffered requests without yielding. io_uring only. `0` disables. |

## Diagnostics

Expand Down
6 changes: 3 additions & 3 deletions scripts/ci/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ def replace(path: Path, begin: str, end: str, content: str) -> None:
# Servers compared in the KDE-style latency distribution chart: only the ones
# run under the identical wrk -t12 -c400 profile every CI cycle (the
# *_tuned entries use a different, lower-concurrency profile and aren't
# comparable here; cwist_c1m_arena1/cwist_c1m_drainchunk/cwist_c1m_lateoff/
# cwist_sharded are targeted A/B legs against the plain cwist_c1m row, not
# part of the standing cross-framework comparison set).
# comparable here; cwist_c1m_arena1/cwist_c1m_drainchunk/cwist_sharded are
# targeted A/B legs against the plain cwist_c1m row, not part of the
# standing cross-framework comparison set).
_LATENCY_KDE_SERVERS = [
("CWIST Classic", "cwist", "#22c55e"),
("CWIST", "cwist_c1m", "#10b981"),
Expand Down
13 changes: 3 additions & 10 deletions src/sys/app/app.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
#include "../../net/http/simd_parser.h"
#include "../../net/websocket/ws_async_internal.h"
/* I/O headers (internal) */
#include "../io/reactor_rx.h"
#include "../io/async_debug.h"
/* Core headers */
#include <cwist/core/sstring/sstring.h>
Expand Down Expand Up @@ -3313,15 +3312,9 @@ cwist_async_action_t cwist_app_http_handler_async(int client_fd, cwist_http_asyn
/* Jeungseung Gaebang Scaling for priority weighting. */
uint32_t priority_weight = ((mixed_seed * 16777619U) >> 8) % 100;

/* One pipeline must not monopolize this reactor's other connections,
* unless this callback runs in a late round (issue #319 experiment): the
* connection has already waited past CWIST_REACTOR_LATE_US, so it serves
* its buffered pipeline now instead of yielding behind the others. */
const bool late = cwist_reactor_dispatch_late();
/* One pipeline must not monopolize this reactor's other connections. */
const unsigned int yield_batch = app_http_yield_batch();
const unsigned int turn_max =
late ? CWIST_HTTP_REQUESTS_PER_TURN_MAX : app_http_requests_per_turn();
for (unsigned int handled = 0; handled < turn_max; ++handled) {
for (unsigned int handled = 0; handled < app_http_requests_per_turn(); ++handled) {
cwist_http_request *req = NULL;
cwist_http_parse_error_t perr = CWIST_HTTP_PARSE_OK;
cwist_recv_status_t st = cwist_http_receive_request_nb(conn, &req, &perr);
Expand Down Expand Up @@ -3370,7 +3363,7 @@ cwist_async_action_t cwist_app_http_handler_async(int client_fd, cwist_http_asyn
* event, but serving the whole turn inline serializes every other
* connection on this reactor. Exit to the continuation path below,
* which reposts this connection so it resumes after the ready CQEs. */
if (!late && (handled + 1) % yield_batch == 0 && conn->len > 0) break;
if ((handled + 1) % yield_batch == 0 && conn->len > 0) break;
}
if (conn->len > 0) {
/* Buffered work cannot wait for another read event. The continuation
Expand Down
68 changes: 1 addition & 67 deletions src/sys/io/reactor.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,12 +263,6 @@ struct cwist_reactor {
/* Owned by the run thread: read only after reactor_on_run_thread(), so
* other threads never touch it. */
bool dispatching;
/* True while the run thread dispatches a callback of a round that has
* run past CWIST_REACTOR_LATE_US (issue #319 experiment). Run thread
* only; read through cwist_reactor_dispatch_late(). */
bool dispatch_late;
uint64_t late_rounds; /* Rounds that crossed the threshold. */
uint64_t late_callbacks; /* Callbacks dispatched in late mode. */
/* RX-uring completion handler for tagged (low-bit set) RECV SQEs;
* registered by the HTTP layer once per worker reactor. */
cwist_rx_cb_t rx_cb;
Expand Down Expand Up @@ -375,10 +369,6 @@ static uint64_t latency_probe_percentile(const uint64_t *buckets, uint64_t count

static void latency_probe_dump(const cwist_reactor_t *reactor) {
if (!latency_probe_enabled()) return;
if (reactor->late_rounds)
fprintf(stderr, "[latency-probe] pid=%d late_rounds=%llu late_callbacks=%llu\n",
(int)getpid(), (unsigned long long)reactor->late_rounds,
(unsigned long long)reactor->late_callbacks);
for (int phase = 0; phase < 2; phase++) {
const uint64_t *b = reactor->probe[phase].buckets;
uint64_t count = reactor->probe[phase].count;
Expand Down Expand Up @@ -1423,39 +1413,6 @@ static uint64_t reactor_round_budget_us(void) {
#endif
#endif

#ifdef __linux__
/* Late-round threshold (issue #319 experiment). Once a dispatch round has
* run this long since its completions were reaped, every remaining callback
* of the round is dispatched in late mode: its re-arm (and any parked so
* far) is submitted right after it instead of at round end, foreign-thread
* posts are drained after each callback, and the HTTP layer serves the
* connection's buffered pipeline without yielding. The connections behind a
* slow round have already waited the threshold; late mode stops them from
* waiting for the rest of it too. CWIST_REACTOR_LATE_US overrides the
* 3000 us default; 0 disables. */
static uint64_t reactor_late_ns(void) {
static _Atomic long cached = -1;
long v = atomic_load_explicit(&cached, memory_order_relaxed);
if (v < 0) {
const char *s = getenv("CWIST_REACTOR_LATE_US");
if (s && *s) {
char *end = NULL;
long parsed = strtol(s, &end, 10);
v = (end != s && *end == '\0' && parsed >= 0 && parsed < 3600000000L) ? parsed : 3000;
} else {
v = 3000;
}
atomic_store_explicit(&cached, v, memory_order_relaxed);
}
return (uint64_t)v * 1000ull;
}

bool cwist_reactor_dispatch_late(void) {
const cwist_reactor_t *r = t_run_reactor;
return r && r->dispatching && r->dispatch_late;
}
#endif

void cwist_reactor_run(cwist_reactor_t *reactor) {
if (!reactor) return;
__atomic_store_n(&reactor->running, true, __ATOMIC_RELEASE);
Expand All @@ -1464,7 +1421,6 @@ void cwist_reactor_run(cwist_reactor_t *reactor) {
#ifdef __linux__
if (!reactor->impl.use_epoll) {
const uint64_t cq_grace_ns = reactor_cq_grace_ns();
const uint64_t late_ns = reactor_late_ns();
while (reactor_running(reactor)) {
reactor_drain_posts(reactor);
reactor_run_timers(reactor);
Expand Down Expand Up @@ -1556,7 +1512,7 @@ void cwist_reactor_run(cwist_reactor_t *reactor) {
reactor->dispatching = true;
const uint64_t round_budget_us = reactor_round_budget_us();
uint64_t round_start_ns = 0;
if (round_budget_us || late_ns) {
if (round_budget_us) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
round_start_ns = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
Expand All @@ -1576,19 +1532,7 @@ void cwist_reactor_run(cwist_reactor_t *reactor) {
uint32_t drain_chunk = reactor_drain_chunk();
uint32_t since_drain = 0;
uint32_t since_budget_check = 0;
bool round_late = false;
while (head != tail) {
if (late_ns && !round_late) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now_ns =
(uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
if (now_ns - round_start_ns >= late_ns) {
round_late = true;
reactor->dispatch_late = true;
reactor->late_rounds++;
}
}
struct io_uring_cqe *cqe = &reactor->impl.cqes[head & *reactor->impl.cq_ring_mask];
uint64_t user_data = cqe->user_data;
if (user_data & 1u) {
Expand Down Expand Up @@ -1638,15 +1582,6 @@ void cwist_reactor_run(cwist_reactor_t *reactor) {
}
}
head++;
if (round_late) {
/* Late mode: nothing this connection queued waits for
* the rest of the round. */
reactor->late_callbacks++;
__atomic_store_n(reactor->impl.cq_head, head, __ATOMIC_RELEASE);
flush_deferred(reactor);
reactor_drain_posts(reactor);
continue;
}
if (drain_chunk && ++since_drain >= drain_chunk && head != tail) {
since_drain = 0;
__atomic_store_n(reactor->impl.cq_head, head, __ATOMIC_RELEASE);
Expand All @@ -1671,7 +1606,6 @@ void cwist_reactor_run(cwist_reactor_t *reactor) {
}
}
reactor->dispatching = false;
reactor->dispatch_late = false;
__atomic_store_n(reactor->impl.cq_head, head, __ATOMIC_RELEASE);
queue_deferred(reactor);
}
Expand Down
10 changes: 0 additions & 10 deletions src/sys/io/reactor_rx.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,8 @@ bool cwist_reactor_recv_arm(cwist_reactor_t *reactor, int fd, void *buf, unsigne
* wrapper in cwist_reactor_run. */
void cwist_reactor_probe_record(cwist_reactor_t *reactor, bool queue, uint64_t sample_us);

/* True while the calling thread dispatches a reactor callback in late mode:
* the round it belongs to has run past CWIST_REACTOR_LATE_US (issue #319
* experiment), so the connection should finish its buffered work now
* instead of yielding. False off the run thread and outside dispatch. */
bool cwist_reactor_dispatch_late(void);

#else /* !__linux__ */

static inline bool cwist_reactor_dispatch_late(void) {
return false;
}

static inline bool cwist_rx_uring_env_enabled(void) {
return false;
}
Expand Down
Loading
Loading