Replies: 11 comments
Hi, that may be because the throughput bottleneck is in the sending side (client) or in the network throughput. In these cases
I am not sure about the reason for the On the other hand, if the network throughput is the bottleneck, the |
|
Thanks - I don't suspect the network. On a 10G link I offer ~10 Gbps (1472B datagrams) Setup (server pinned to CPU0; GRO is negotiated to the server via the client's --gsro): Switching to a blocking receive drops the RX core to ~27% idle, throughput unchanged. Two factors:
Also (separate from CPU): with stock recvmsg+MSG_DONTWAIT we couldn't reach line rate on Questions:
|
From the almost 10Gbps and the 27% idle it seems that your server (and probably client) machines are strong to process the data without the need for GSO/GRO. Therefore, as I wrote above, it may be that the current
My understanding is that the |
|
Thanks @davidBar-On , Sharing isolation results, then the cmsg point. Findings: the MSG_DONTWAIT busy-poll is the dominant cost — blocking (V1→V2) recovers ~28% idle on the pinned core. Plain recv() (V2→V3) adds ~4–5% more. No loss penalty (all <0.02%).
On cmsg: agreed blksize correctly drives the parse loop. My only point is that the cmsg value is discarded — in iperf_udp_recv(): r = Nread_gro(sp->socket, sp->buffer, size, Pudp, &dgram_sz);
dgram_sz = sp->settings->blksize; // overwrites the cmsg gso_size on the next lineSo recv_msg_gro()'s cmsg walk never reaches the parser, which is why plain recv() parsed identically with less CPU. The cmsg is still useful for validation / non-uniform datagrams — a safe approach: use gso_size when present, fall back to blksize, validate on mismatch. On MSG_DONTWAIT: deferring to @gegles (#1925) on whether the non-blocking receive was intentional. On my setup blocking recovers ~27% of the RX core with no loss regression. Happy to send a PR — blocking-only, or blocking + recv() with cmsg fallback? |
I think that sending a blocking-only PR is a good idea (note that I am not from the iperf3 maintenance team). I don't suggest to include the A major potential issue with removing the
OK. I understand now. Actually, I think that the main problem is that In any case, if you test this approach and have conclusion / proposed changes, I suggest to send them as a separate PR and not add them to the above non-blocking PR, to keep that PR as simple as possible. |
|
Thanks for doing the isolation. The results make a good case that For design context, I agree that the cmsg question should be handled separately. One caution: the negotiated For the blocking PR, please test normal termination, a short |
|
Opened #2069 with the blocking-only change as discussed (removes MSG_DONTWAIT from the GRO receive path). Kept the cmsg cleanup out to keep it minimal, per the earlier feedback — happy to do that as a separate PR later. |
|
I also noticed that with --gsro enabled, iperf_time_now() is called many times in the RX path — effectively once per coalesced segment. Would it be acceptable to timestamp once per GRO receive operation and reuse that timestamp for jitter processing, to reduce CPU cost? Since a coalesced batch is delivered as a single kernel event, the per-segment timestamps effectively reflect loop-iteration timing rather than true arrival. Is per-segment timestamping required for jitter accuracy in iperf3's current model, or would a batched (per-receive) timestamp be an acceptable tradeoff? Happy to send a separate PR (independent of #2069) with before/after CPU numbers if this sounds reasonable. |
|
Yes, I think a separate PR to timestamp once per GRO receive operation is reasonable. Once GRO has coalesced multiple datagrams, iperf no longer has genuine per-segment arrival timestamps. Calling The main caveat is that this changes jitter semantics: the result becomes a batch-level approximation, not a more accurate per-datagram measurement. Please include before/after CPU profiling and compare the reported jitter with GRO disabled under controlled pacing/jitter. The behavior should also be documented so users do not interpret GRO-mode jitter as true wire-level per-segment timing. Keeping this independent of #2069 is the right approach. |
|
Thanks — that matches our reasoning. We ran the measurements you asked for. Setup: single UDP stream at ~9.57 Gbps, GRO on, receive core isolated with CPU (GRO on, 9.57 Gbps):
Hoisting recovers ~3.5% of the receive core, with no throughput/loss regression (reproduced on a second platform). Jitter vs GRO off (controlled pacing): GRO-off (true per-datagram) 0.022 ms vs GRO-on (hoisted) 0.000 ms — same sub-millisecond ballpark, so the batch-level value doesn't distort reported jitter. Docs: the PR will note in I'll open this as a separate PR, independent of #2069. |
|
Thanks—the CPU result is useful and supports continuing with a separate optimization PR. I don’t think the jitter result supports the “no distortion” conclusion yet, though. The current receive loop updates the jitter EWMA once per segment. If one arrival timestamp is reused across every segment in a GRO batch, each batch applies many near-zero updates consecutively, which can drive the reported jitter toward zero and make it dependent on the coalesced batch size. The change from 0.022 ms to 0.000 ms may be evidence of that effect rather than equivalence. Please report the raw JSON If the intended metric is batch-level jitter, I think the cleaner model is to update the jitter estimator once per GRO batch using a clearly defined representative send timestamp, rather than updating it once per segment with the same receive timestamp. The documentation should then describe that batch-level semantic explicitly. |
Uh oh!
There was an error while loading. Please reload this page.
Hi all,
I'm seeing high CPU utilization on the RX side with UDP GRO enabled (
--gsro) when the iperf3 server is pinned to a single core usingtaskset. The pinned RX core becomes saturated.While looking at the receive path, I noticed that
nread_gro()/recv_msg_gro()userecvmsg()withMSG_DONTWAITand parse theUDP_GROcmsg. Iniperf_udp_recv(), however, the returned segment size appears to be overwritten withblksizeand not used afterwards.As an experiment, I replaced the
recvmsg()path with a plainrecv()and observed lower CPU utilization with no noticeable throughput change in my testing.Is this expected? Has anyone else observed similar behavior with
--gsroenabled? I'm also interested in understanding whether theUDP_GROcmsg information is required by iperf3's current receive path, and whetherrecvmsg()/cmsg handling (or the use ofMSG_DONTWAIT) is expected to contribute measurable overhead in this case.Thanks.
All reactions