Loading src/posix/udp.cpp +4 −2 Changes for src/posix/udp.cpp: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -649,8 +649,10 @@ int recvBatchRaw(SOCKET sock, int batch, bool gro_enabled) { } } struct timespec timeout = {0, 0}; // non-blocking int received = ::recvmmsg(sock, g_recv_msgs, batch, MSG_DONTWAIT, &timeout); // No timeout: MSG_DONTWAIT is what makes this non-blocking. The kernel // checks a timeout after every datagram, so a zero one ends the call after // the first and every "batch" is exactly one datagram however many queued. int received = ::recvmmsg(sock, g_recv_msgs, batch, MSG_DONTWAIT, nullptr); quicPerf().recvmmsg_calls.fetch_add(1, std::memory_order_relaxed); if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) quicPerf().recv_eagain.fetch_add(1, std::memory_order_relaxed); Loading src/quic.cpp +4 −9 Changes for src/quic.cpp: 4 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -122,15 +122,10 @@ ThreadPool& quicDispatchPool() { // just cause cache-line/AES-NI-unit contention between worker threads for // no benefit — sized near physical core count instead. // // Off by default: batch-size measurement on this sandbox's loopback bulk // benchmark showed recvmmsg batches are essentially always exactly 1 // datagram (a single CPU-bound consumer thread never lets the kernel queue // build up any deeper — see project memory), meaning there is nothing to // fan out across on THIS workload regardless of how much decrypt costs. // This is built for deployments where real network jitter/bursty delivery // produces genuinely multi-packet batches, which this sandbox cannot // reproduce to validate a throughput gain against — hence opt-in // (QUIC_PARALLEL_DECRYPT=1) rather than a new default. // Off by default (QUIC_PARALLEL_DECRYPT=1 enables it): on the loopback bulk // benchmark, with GRO batches of ~24 packets, the fan-out costs more than it // saves, 270 MB/s against 800+ without. Each packet is one task, which is far // too fine-grained for a decrypt of under a microsecond per KB. ThreadPool& quicDecryptPool() { static ThreadPool pool((std::max<unsigned>)(2, std::thread::hardware_concurrency())); return pool; Loading test/quic_parallel_decrypt_test.cpp +6 −13 Changes for test/quic_parallel_decrypt_test.cpp: 6 added lines, 13 removed lines. Original line number Diff line number Diff line Loading @@ -188,19 +188,12 @@ static void test_forced_batch_real_decrypt() { client.sendStreamData(sid, chunk.data(), chunk.size(), i == N - 1); } // Assemble a real, valid N-packet batch out of N individually-received // datagrams. This sandbox's recvmmsg() only ever returns one datagram // per call regardless of how many are actually queued (confirmed via // FIONREAD during development: the kernel already has all N queued, but // recvmmsg(vlen=64) still returns 1 at a time here) — an environment // quirk, not something under this codebase's control, and consistent // with this same session's earlier finding that real bulk-transfer // benchmarks also never see recvbatch avg/max above 1.0 in this // sandbox. Each datagram is copied out of the thread_local scratch // buffer immediately (DatagramView is only valid until the next // recvBatchViews() call on this thread) into test-owned storage, so the // resulting batch is still N genuinely-received, genuinely-encrypted // packets — just assembled by the test instead of by a single syscall. // Assemble a real, valid N-packet batch out of the received datagrams. // Each datagram is copied out of the thread_local scratch buffer // immediately (DatagramView is only valid until the next recvBatchViews() // call on this thread) into test-owned storage, so the resulting batch // is still N genuinely-received, genuinely-encrypted packets — just // assembled by the test instead of by a single syscall. // processApplicationPacketsBatchParallel() itself has no way to tell // the difference: it only ever sees a std::vector<std::pair<...>>. // Loading Loading
src/posix/udp.cpp +4 −2 Changes for src/posix/udp.cpp: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -649,8 +649,10 @@ int recvBatchRaw(SOCKET sock, int batch, bool gro_enabled) { } } struct timespec timeout = {0, 0}; // non-blocking int received = ::recvmmsg(sock, g_recv_msgs, batch, MSG_DONTWAIT, &timeout); // No timeout: MSG_DONTWAIT is what makes this non-blocking. The kernel // checks a timeout after every datagram, so a zero one ends the call after // the first and every "batch" is exactly one datagram however many queued. int received = ::recvmmsg(sock, g_recv_msgs, batch, MSG_DONTWAIT, nullptr); quicPerf().recvmmsg_calls.fetch_add(1, std::memory_order_relaxed); if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) quicPerf().recv_eagain.fetch_add(1, std::memory_order_relaxed); Loading
src/quic.cpp +4 −9 Changes for src/quic.cpp: 4 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -122,15 +122,10 @@ ThreadPool& quicDispatchPool() { // just cause cache-line/AES-NI-unit contention between worker threads for // no benefit — sized near physical core count instead. // // Off by default: batch-size measurement on this sandbox's loopback bulk // benchmark showed recvmmsg batches are essentially always exactly 1 // datagram (a single CPU-bound consumer thread never lets the kernel queue // build up any deeper — see project memory), meaning there is nothing to // fan out across on THIS workload regardless of how much decrypt costs. // This is built for deployments where real network jitter/bursty delivery // produces genuinely multi-packet batches, which this sandbox cannot // reproduce to validate a throughput gain against — hence opt-in // (QUIC_PARALLEL_DECRYPT=1) rather than a new default. // Off by default (QUIC_PARALLEL_DECRYPT=1 enables it): on the loopback bulk // benchmark, with GRO batches of ~24 packets, the fan-out costs more than it // saves, 270 MB/s against 800+ without. Each packet is one task, which is far // too fine-grained for a decrypt of under a microsecond per KB. ThreadPool& quicDecryptPool() { static ThreadPool pool((std::max<unsigned>)(2, std::thread::hardware_concurrency())); return pool; Loading
test/quic_parallel_decrypt_test.cpp +6 −13 Changes for test/quic_parallel_decrypt_test.cpp: 6 added lines, 13 removed lines. Original line number Diff line number Diff line Loading @@ -188,19 +188,12 @@ static void test_forced_batch_real_decrypt() { client.sendStreamData(sid, chunk.data(), chunk.size(), i == N - 1); } // Assemble a real, valid N-packet batch out of N individually-received // datagrams. This sandbox's recvmmsg() only ever returns one datagram // per call regardless of how many are actually queued (confirmed via // FIONREAD during development: the kernel already has all N queued, but // recvmmsg(vlen=64) still returns 1 at a time here) — an environment // quirk, not something under this codebase's control, and consistent // with this same session's earlier finding that real bulk-transfer // benchmarks also never see recvbatch avg/max above 1.0 in this // sandbox. Each datagram is copied out of the thread_local scratch // buffer immediately (DatagramView is only valid until the next // recvBatchViews() call on this thread) into test-owned storage, so the // resulting batch is still N genuinely-received, genuinely-encrypted // packets — just assembled by the test instead of by a single syscall. // Assemble a real, valid N-packet batch out of the received datagrams. // Each datagram is copied out of the thread_local scratch buffer // immediately (DatagramView is only valid until the next recvBatchViews() // call on this thread) into test-owned storage, so the resulting batch // is still N genuinely-received, genuinely-encrypted packets — just // assembled by the test instead of by a single syscall. // processApplicationPacketsBatchParallel() itself has no way to tell // the difference: it only ever sees a std::vector<std::pair<...>>. // Loading