Linux virtualization list
 help / color / mirror / Atom feed
* [PATCH v4] vhost/vsock: batch RX used-ring updates
@ 2026-09-06  1:36 Jia Jia
  2026-09-06  4:07 ` Michael S. Tsirkin
  0 siblings, 1 reply; 2+ messages in thread
From: Jia Jia @ 2026-09-06  1:36 UTC (permalink / raw)
  To: sgarzare
  Cc: stefanha, mst, jasowang, eperezma, kvm, virtualization, netdev,
	linux-kernel

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 and array capacity.  Flush when the batch reaches its limit or
before 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:

The test used vsock_perf with a fresh Guest for each state and 20 paired
runs. The Guest receiver used:

  vsock_perf --port PORT --buf-size 64M --vsk-size 64M --rcvlowat 1

The Host sender used:

  vsock_perf --sender 3 --port PORT --bytes 1G \
      --buf-size SEND_BUF --vsk-size 64M

The table reports geometric mean Guest RX throughput.

  SEND_BUF   baseline RX   batching RX   change
             (Gbit/s)       (Gbit/s)
  256 B      0.0795724      0.0831509      +4.497%
  512 B      0.1194885      0.1210297      +1.290%
  4 KiB      0.7208273      0.7242053      +0.469%
  64 KiB     2.1712797      2.1951941      +1.101%

As supplementary data, perf stat measured the vhost worker cycles and
instructions per GiB in 10 paired runs. The patched implementation
reduced cycles by 3.846%, 2.162%, and 4.109%, and instructions by
2.548%, 2.084%, and 4.637% for 256 B, 4 KiB, and 64 KiB, respectively.

An AF_VSOCK request-response latency test with 10 AB/BA pairs showed no
consistent RTT change: -0.102% for 256-byte messages (4/10 pairs lower) and
+3.281% for 4-KiB messages (5/10 pairs lower), using arithmetic mean RTTs.

Signed-off-by: Jia Jia <physicalmtea@gmail.com>
Acked-by: Eugenio Pérez <eperezma@redhat.com>
---
Changes in v4:
- Simplify the performance description.
- Add a brief AF_VSOCK RTT measurement.
- Keep the batch limit within the used ring and scratch arrays, without
  duplicating the worker weight limit.
- Flush after adding a used entry when the batch reaches the limit, and
  remove the redundant flush in the empty-queue path.
---
 drivers/vhost/vsock.c | 47 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
index 9aaab6bb8061..7a13abe73345 100644
--- a/drivers/vhost/vsock.c
+++ b/drivers/vhost/vsock.c
@@ -103,12 +103,35 @@ 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);
+	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;
+}
+
 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 +143,11 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 	if (!vq_meta_prefetch(vq))
 		goto out;
 
+	/* Keep the batch within the used ring and the scratch arrays. */
+	used_limit = min_t(unsigned int, vq->num, vq->dev->iov_limit);
+	if (unlikely(!used_limit))
+		goto out;
+
 	/* Avoid further vmexits, we're already processing the virtqueue */
 	vhost_disable_notify(&vsock->dev, vq);
 
@@ -150,9 +178,16 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
 
 		if (head == vq->num) {
 			virtio_vsock_skb_queue_head(&vsock->send_pkt_queue, skb);
+
+			/* Flush completed buffers before re-enabling notifications. */
+			if (vhost_vsock_flush_used(vq, used_count)) {
+				added = true;
+				used_count = 0;
+			}
+
 			/* We cannot finish yet if more buffers snuck in while
 			 * re-enabling notify.
 			 */
 			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
 				vhost_disable_notify(&vsock->dev, vq);
 				continue;
@@ -230,8 +265,13 @@ 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);
+		used_count++;
+		if (used_count == used_limit) {
+			added |= vhost_vsock_flush_used(vq, used_count);
+			used_count = 0;
+		}
 
 		VIRTIO_VSOCK_SKB_CB(skb)->offset += payload_len;
 		total_len += payload_len;
@@ -264,6 +304,9 @@ 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.34.1

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH v4] vhost/vsock: batch RX used-ring updates
  2026-09-06  1:36 [PATCH v4] vhost/vsock: batch RX used-ring updates Jia Jia
@ 2026-09-06  4:07 ` Michael S. Tsirkin
  0 siblings, 0 replies; 2+ messages in thread
From: Michael S. Tsirkin @ 2026-09-06  4:07 UTC (permalink / raw)
  To: Jia Jia
  Cc: sgarzare, stefanha, jasowang, eperezma, kvm, virtualization,
	netdev, linux-kernel

On Sun, Sep 06, 2026 at 09:36:59AM +0800, Jia Jia wrote:
> 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 and array capacity.  Flush when the batch reaches its limit or
> before 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:
> 
> The test used vsock_perf with a fresh Guest for each state and 20 paired
> runs. The Guest receiver used:
> 
>   vsock_perf --port PORT --buf-size 64M --vsk-size 64M --rcvlowat 1
> 
> The Host sender used:
> 
>   vsock_perf --sender 3 --port PORT --bytes 1G \
>       --buf-size SEND_BUF --vsk-size 64M
> 
> The table reports geometric mean Guest RX throughput.
> 
>   SEND_BUF   baseline RX   batching RX   change
>              (Gbit/s)       (Gbit/s)
>   256 B      0.0795724      0.0831509      +4.497%
>   512 B      0.1194885      0.1210297      +1.290%
>   4 KiB      0.7208273      0.7242053      +0.469%
>   64 KiB     2.1712797      2.1951941      +1.101%


I will be frank I don't find the numbers compelling enough
to bother with this trickery.

vsock wasn't optimized all that much for small packets -
first of all, it is doing most of its work in a work item
so we are already at the mercy of the scheduler.

That's more work but you will see a much bigger win for bw and
latency by just handling small packets directly in the cb
if you can.

Looking at batching: wakeup per packet in rx_work, lock_sock per packet,
sk->sk_write_space per rx packet are all high overhead things we are
doing in the data path that are likely easier to handle
and will give you more bang for the buck.





> 
> As supplementary data, perf stat measured the vhost worker cycles and
> instructions per GiB in 10 paired runs. The patched implementation
> reduced cycles by 3.846%, 2.162%, and 4.109%, and instructions by
> 2.548%, 2.084%, and 4.637% for 256 B, 4 KiB, and 64 KiB, respectively.
> 
> An AF_VSOCK request-response latency test with 10 AB/BA pairs showed no
> consistent RTT change: -0.102% for 256-byte messages (4/10 pairs lower) and
> +3.281% for 4-KiB messages (5/10 pairs lower), using arithmetic mean RTTs.
> 
> Signed-off-by: Jia Jia <physicalmtea@gmail.com>
> Acked-by: Eugenio Pérez <eperezma@redhat.com>
> ---
> Changes in v4:
> - Simplify the performance description.
> - Add a brief AF_VSOCK RTT measurement.
> - Keep the batch limit within the used ring and scratch arrays, without
>   duplicating the worker weight limit.
> - Flush after adding a used entry when the batch reaches the limit, and
>   remove the redundant flush in the empty-queue path.
> ---
>  drivers/vhost/vsock.c | 47 +++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 45 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/vhost/vsock.c b/drivers/vhost/vsock.c
> index 9aaab6bb8061..7a13abe73345 100644
> --- a/drivers/vhost/vsock.c
> +++ b/drivers/vhost/vsock.c
> @@ -103,12 +103,35 @@ 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);
> +	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;
> +}
> +


flush where? add where? return what? these apis don't make it
easier to read code.

>  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 +143,11 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
>  	if (!vq_meta_prefetch(vq))
>  		goto out;
>  
> +	/* Keep the batch within the used ring and the scratch arrays. */

what "the batch"? this is the 1st time code mentions any batch.

> +	used_limit = min_t(unsigned int, vq->num, vq->dev->iov_limit);
> +	if (unlikely(!used_limit))
> +		goto out;
> +
>  	/* Avoid further vmexits, we're already processing the virtqueue */
>  	vhost_disable_notify(&vsock->dev, vq);
>  
> @@ -150,9 +178,16 @@ vhost_transport_do_send_pkt(struct vhost_vsock *vsock,
>  
>  		if (head == vq->num) {
>  			virtio_vsock_skb_queue_head(&vsock->send_pkt_queue, skb);
> +
> +			/* Flush completed buffers before re-enabling notifications. */


redundant - i can see this is what it does but why? and what does flush mean here?

> +			if (vhost_vsock_flush_used(vq, used_count)) {
> +				added = true;
> +				used_count = 0;
> +			}
> +
>  			/* We cannot finish yet if more buffers snuck in while
>  			 * re-enabling notify.
>  			 */
>  			if (unlikely(vhost_enable_notify(&vsock->dev, vq))) {
>  				vhost_disable_notify(&vsock->dev, vq);
>  				continue;
> @@ -230,8 +265,13 @@ 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);
> +		used_count++;
> +		if (used_count == used_limit) {
> +			added |= vhost_vsock_flush_used(vq, used_count);


bitwise or on a boolean likely not what was intended.

> +			used_count = 0;
> +		}
>  
>  		VIRTIO_VSOCK_SKB_CB(skb)->offset += payload_len;
>  		total_len += payload_len;
> @@ -264,6 +304,9 @@ 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);
> +


and here.

>  	if (added)
>  		vhost_signal(&vsock->dev, vq);
> 
> -- 
> 2.34.1


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-09-06  4:07 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-06  1:36 [PATCH v4] vhost/vsock: batch RX used-ring updates Jia Jia
2026-09-06  4:07 ` Michael S. Tsirkin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox