[PATCH] vhost/vsock: batch RX used-ring updates
Jia Jia <[email protected]>
| Newsgroups | dev.linux.lists.virtualization,org.kernel.vger.kvm,org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <[email protected]> |
vhost_transport_do_send_pkt() calls vhost_add_used() for every Guest RX
buffer even though it delays the Guest signal until the worker finishes.
Each call publishes one used entry and updates the used index separately.
Collect the completed buffer heads in the arrays already allocated for the
virtqueue and publish them with vhost_add_used_n(). Bound the batch by the
ring size, array capacity, and worker packet budget. Flush before
re-enabling notifications or leaving the worker.
Each used entry describes one completed RX buffer and keeps its actual used
length, so set nheads to 1 for every entry. This patch does not change
negotiated features or compress multiple buffers into one used entry.
This patch is limited to the current skb-based vhost-vsock RX path.
Performance:
Tested with a QEMU/KVM guest on a host with 4 online CPUs, using 2 vCPUs
pinned to host CPUs 2 and 3, QEMU 10.2.1, q35, 1536 MiB, and Linux
7.2.0-rc3-next-20260713-next-debug-kasan. The vhost-vsock source is based
on linux-next master at 49362394dad7df66c274c867a271394c10ca2bb8.
Current vhost-vsock does not implement VIRTIO_F_IN_ORDER or
VIRTIO_F_RING_PACKED, so both configurations used packed=off and
in_order=off:
baseline: RX batching=off
vhost-vsock RX batching: RX batching=on
The test used vsock_perf. The Guest receiver was started with:
vsock_perf --port PORT --buf-size 64M --vsk-size 64M --rcvlowat 1
The Host sender was started with:
vsock_perf --sender 3 --port PORT --bytes BYTES \
--buf-size SEND_BUF --vsk-size 64M
Each workload transferred BYTES=1 GiB. The SEND_BUF values were 256 B
(SEND_BUF=256), 512 B (SEND_BUF=512), 4 KiB (SEND_BUF=4K), and 64 KiB
(SEND_BUF=64K).
Each state used a fresh Guest. Each workload uses 20 paired runs, with 10
runs in each order. The reported values are
Guest RX throughput in Gbits/s. The baseline and batching columns are the
geometric means over the 20 runs; change is batching / baseline - 1,
computed from the unrounded values:
workload baseline RX batching RX change faster
256 B 0.0795724 0.0831509 +4.497% 20/20
512 B 0.1194885 0.1210297 +1.290% 14/20
4 KiB 0.7208273 0.7242053 +0.469% 11/20
64 KiB 2.1712797 2.1951941 +1.101% 13/20
For reference, the table below gives the 95% normal-approximation intervals
obtained from the 20 paired log(batching / baseline) values:
workload paired 95% interval
256 B +3.985% to +5.011%
512 B +0.206% to +2.385%
4 KiB -1.442% to +2.416%
64 KiB -1.474% to +3.745%
All transfers passed byte-count checks, and no kernel errors were observed
in the logs. The 256-byte workload improved in every pair. The 512 B
workload was faster in 14 of 20 pairs, with a small gain. The 4 KiB and
64 KiB workloads showed no material throughput change; the difference
between their results may be due to scheduling and execution variation.
Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Jia Jia <[email protected]>
---
drivers/vhost/vsock.c | 41 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 39 insertions(+), 2 deletions(-)
diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 9aaab6bb8..d8050cca2 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -103,12 +103,37 @@ static bool vhost_transport_has_remote_cid(struct vsock_sock *vsk, u32 cid)
return found;
}
+static bool vhost_vsock_flush_used(struct vhost_virtqueue *vq,
+ unsigned int *used_count)
+{
+ if (!*used_count)
+ return false;
+
+ vhost_add_used_n(vq, vq->heads, vq->nheads, *used_count);
+ *used_count = 0;
+ return true;
+}
+
+static void vhost_vsock_add_used(struct vhost_virtqueue *vq,
+ unsigned int *used_count,
+ unsigned int head, unsigned int len)
+{
+ struct vring_used_elem *used = &vq->heads[*used_count];
+
+ used->id = cpu_to_vhost32(vq, head);
+ used->len = cpu_to_vhost32(vq, len);
+ vq->nheads[*used_count] = 1;
+ (*used_count)++;
+}
+
static void
vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
struct vhost_virtqueue *vq)
{
struct vhost_virtqueue *tx_vq = &vsock->vqs[VSOCK_VQ_TX];
int pkts = 0, total_len = 0;
+ unsigned int used_count = 0;
+ unsigned int used_limit;
bool added = false;
bool restart_tx = false;
@@ -120,6 +145,12 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
if (!vq_meta_prefetch(vq))
goto out;
+ used_limit = min_t(unsigned int, vq->num,
+ min_t(unsigned int, vq->dev->iov_limit,
+ vq->dev->weight));
+ if (unlikely(!used_limit))
+ goto out;
+
/* Avoid further vmexits, we're already processing the virtqueue */
vhost_disable_notify(&vsock->dev, vq);
@@ -134,9 +165,13 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
u32 offset;
int head;
+ if (used_count == used_limit)
+ added |= vhost_vsock_flush_used(vq, &used_count);
+
skb = virtio_vsock_skb_dequeue(&vsock->send_pkt_queue);
if (!skb) {
+ added |= vhost_vsock_flush_used(vq, &used_count);
vhost_enable_notify(&vsock->dev, vq);
break;
}
@@ -153,6 +188,7 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
/* We cannot finish yet if more buffers snuck in while
* re-enabling notify.
*/
+ added |= vhost_vsock_flush_used(vq, &used_count);
if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
vhost_disable_notify(&vsock->dev, vq);
continue;
@@ -230,8 +266,8 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
*/
virtio_transport_deliver_tap_pkt(skb);
- vhost_add_used(vq, head, sizeof(*hdr) + payload_len);
- added = true;
+ vhost_vsock_add_used(vq, &used_count, head,
+ sizeof(*hdr) + payload_len);
VIRTIO_VSOCK_SKB_CB(skb)->offset += payload_len;
total_len += payload_len;
@@ -264,6 +300,7 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
virtio_transport_consume_skb_sent(skb, true);
}
} while(likely(!vhost_exceeds_weight(vq, ++pkts, total_len)));
+ added |= vhost_vsock_flush_used(vq, &used_count);
if (added)
vhost_signal(&vsock->dev, vq);
--
2.53.0