udp: scalability improvements
Subsystems (4)
Paolo Abeni
maintainercover udp: scalability improvements
This patch series implement an idea suggested by Eric Dumazet to reduce the contention of the udp sk_receive_queue lock when the socket is under flood.
An ancillary queue is added to the udp socket, and the socket always tries first to read packets from such queue. If it's empty, we splice the content from sk_receive_queue into the ancillary queue.
The first patch introduces some helpers to keep the udp code small, and the following two implement the ancillary queue strategy. The code is split to hopefully help the reviewing process.
The measured overall gain under udp flood is in the 20-35% range depending on the numa layout and the number of ingress queue used by the relevant nic.
On a single numa node host, the peak tput is now reached when the traffic targeting the udp socket uses multiple nic rx queues, while on current net-next the tput always decreases when moving from a single rx queue to multiple ones.
Paolo Abeni (3):
net/sock: factor out dequeue/peek with offset code
udp: use a separate rx queue for packet reception
udp: keep the sk_receive_queue held when splicing
include/linux/skbuff.h | 7 +++ include/linux/udp.h | 3 + include/net/sock.h | 4 +- include/net/udp.h | 9 +-- include/net/udplite.h | 2 +- net/core/datagram.c | 90 +++++++++++++++------------ net/ipv4/udp.c | 162 +++++++++++++++++++++++++++++++++++++++++++------ net/ipv6/udp.c | 3 +- 8 files changed, 211 insertions(+), 69 deletions(-)
Signature · 1 line
2.9.3
Paolo Abeni
maintainerRFC PATCH 1/3 net/sock: factor out dequeue/peek with offset code
And update __sk_queue_drop_skb() to work on the specified queue. This will help the udp protocol to use an additional private rx queue in a later patch.
--- include/linux/skbuff.h | 7 ++++ include/net/sock.h | 4 +-- net/core/datagram.c | 90 ++++++++++++++++++++++++++++---------------------- 3 files changed, 60 insertions(+), 41 deletions(-)
include/linux/skbuff.h +7 -0
@@ -3056,6 +3056,13 @@ static inline void skb_frag_list_init(struct sk_buff *skb)int __skb_wait_for_more_packets (struct sock *sk,int *err,long *timeo_p,const struct sk_buff *skb); +struct sk_buff *__skb_try_recv_from_queue (struct sock *sk, +struct sk_buff_head *queue, +unsigned int flags, +void (*destructor)(struct sock *sk, +struct sk_buff *skb), +int *peeked,int *off,int *err, +struct sk_buff **last); struct sk_buff *__skb_try_recv_datagram(struct sock *sk, unsigned flags, void (*destructor)(struct sock *sk, struct sk_buff *skb),
include/net/sock.h +2 -2
@@ -2035,8 +2035,8 @@ void sk_reset_timer(struct sock *sk, struct timer_list *timer,void sk_stop_timer (struct sock *sk,struct timer_list *timer); -int __sk_queue_drop_skb (struct sock *sk,struct sk_buff *skb, -unsigned int flags, +int __sk_queue_drop_skb (struct sock *sk,struct sk_buff_head *sk_queue, +struct sk_buff *skb,unsigned int flags,void (*destructor)(struct sock *sk,struct sk_buff *skb));int __sock_queue_rcv_skb (struct sock *sk,struct sk_buff *skb);
net/core/datagram.c +51 -39
133 lines not on this page · raw message
Signature · 1 line
2.9.3
Paolo Abeni
maintainerRFC PATCH 2/3 udp: use a separate rx queue for packet reception
In reply to Paolo Abeniunder udp flood the sk_receive_queue spinlock is heavily contended. This patch try to reduce the contention on such lock adding a second receive queue to the udp sockets; recvmsg() looks first in such queue and, only if empty, tries to fetch the data from sk_receive_queue. The latter is spliced into the newly added queue every time the receive path has to acquire the sk_receive_queue lock.
The accounting of forward allocated memory is still protected with the sk_receive_queue lock, so udp_rmem_release() needs to acquire both locks when the forward deficit is flushed.
On specific scenarios we can end up acquiring and releasing the sk_receive_queue lock multiple times; that will be covered by the next patch
--- include/linux/udp.h | 3 ++ include/net/udp.h | 9 +--- include/net/udplite.h | 2 +- net/ipv4/udp.c | 138 ++++++++++++++++++++++++++++++++++++++++++++------ net/ipv6/udp.c | 3 +- 5 files changed, 131 insertions(+), 24 deletions(-)
include/linux/udp.h +3 -0
@@ -80,6 +80,9 @@ struct udp_sock {struct sk_buff *skb, int nhoff); +/* udp_recvmsg try to use this before splicing sk_receive_queue */ +struct sk_buff_head reader_queue____cacheline_aligned_in_smp ; +/* This field is dirtied by udp_recvmsg() */ int forward_deficit; };
include/net/udp.h +2 -7
@@ -249,13 +249,8 @@ void udp_destruct_sock(struct sock *sk);void skb_consume_udp (struct sock *sk,struct sk_buff *skb,int len);int __udp_enqueue_schedule_skb (struct sock *sk,struct sk_buff *skb);void udp_skb_destructor (struct sock *sk,struct sk_buff *skb); -static inline struct sk_buff * -__skb_recv_udp (struct sock *sk,unsigned int flags,int noblock,int *peeked, -int *off,int *err) -{ -return __skb_recv_datagram (sk, flags | (noblock ?MSG_DONTWAIT :0 ), - udp_skb_destructor, peeked, off, err); -} +struct sk_buff *__skb_recv_udp (struct sock *sk,unsigned int flags, +int noblock,int *peeked,int *off,int *err);static inline struct sk_buff *skb_recv_udp (struct sock *sk,unsigned int flags,int noblock,int *err) {
include/net/udplite.h +1 -1
@@ -26,8 +26,8 @@ static __inline__ int udplite_getfrag(void *from, char *to, int offset,/* Designate sk as UDP-Lite socket */ static inline int udplite_sk_init (struct sock *sk) { +udp_init_sock (sk);udp_sk (sk)->pcflag =UDPLITE_BIT ; - sk->sk_destruct = udp_destruct_sock;return 0 ; }
net/ipv4/udp.c +123 -15
214 lines not on this page · raw message
net/ipv6/udp.c +2 -1
@@ -455,7 +455,8 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,return err; csum_copy_err: -if (!__sk_queue_drop_skb (sk, skb, flags, udp_skb_destructor)) { +if (!__sk_queue_drop_skb (sk, &udp_sk (sk)->reader_queue, skb, flags, + udp_skb_destructor)) {if (is_udp4) {UDP_INC_STATS (sock_net (sk),UDP_MIB_CSUMERRORS , is_udplite);
Signature · 1 line
2.9.3
Paolo Abeni
maintainerRFC PATCH 3/3 udp: keep the sk_receive_queue held when splicing
In reply to Paolo AbeniOn packet reception, when we are forced to splice the sk_receive_queue, we can keep the related lock held, so that we can avoid re-acquiring it, if fwd memory scheduling is required.
--- net/ipv4/udp.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-)
net/ipv4/udp.c +26 -10
@@ -1164,7 +1164,8 @@ int udp_sendpage(struct sock *sk, struct page *page, int offset, }/* fully reclaim rmem/fwd memory allocated for skb */ -static void udp_rmem_release (struct sock *sk,int size,int partial) +static void udp_rmem_release (struct sock *sk,int size,int partial, +int rx_queue_lock_held) {struct udp_sock *up =udp_sk (sk);struct sk_buff_head *sk_queue;
@@ -1181,9 +1182,13 @@ static void udp_rmem_release(struct sock *sk, int size, int partial) } up->forward_deficit =0 ; -/* acquire the sk_receive_queue for fwd allocated memory scheduling */ +/* acquire the sk_receive_queue for fwd allocated memory scheduling, +* if the called don't held it already +*/ sk_queue = &sk->sk_receive_queue; -spin_lock (&sk_queue->lock); +if (!rx_queue_lock_held) +spin_lock (&sk_queue->lock); + sk->sk_forward_alloc += size; amt = (sk->sk_forward_alloc - partial) & ~(SK_MEM_QUANTUM -1 );
@@ -1197,7 +1202,8 @@ static void udp_rmem_release(struct sock *sk, int size, int partial)/* this can save us from acquiring the rx queue lock on next receive */ skb_queue_splice_tail_init (sk_queue, &up->reader_queue); -spin_unlock (&sk_queue->lock); +if (!rx_queue_lock_held) +spin_unlock (&sk_queue->lock); }/* Note: called with reader_queue.lock held.
@@ -1207,10 +1213,16 @@ static void udp_rmem_release(struct sock *sk, int size, int partial)*/ void udp_skb_destructor (struct sock *sk,struct sk_buff *skb) { -udp_rmem_release (sk, skb->dev_scratch,1 ); +udp_rmem_release (sk, skb->dev_scratch,1 ,0 ); }EXPORT_SYMBOL (udp_skb_destructor); +/* as above, but the caller held the rx queue lock, too */ +void udp_skb_dtor_locked (struct sock *sk,struct sk_buff *skb) +{ +udp_rmem_release (sk, skb->dev_scratch,1 ,1 ); +} +/* Idea of busylocks is to let producers grab an extra spinlock * to relieve pressure on the receive_queue spinlock shared by consumer. * Under flood, this means that only one producer can be in line
@@ -1325,7 +1337,7 @@ void udp_destruct_sock(struct sock *sk) total += skb->truesize;kfree_skb (skb); } -udp_rmem_release (sk, total,0 ); +udp_rmem_release (sk, total,0 ,1 );inet_sock_destruct (sk); }
@@ -1397,7 +1409,7 @@ static int first_packet_length(struct sock *sk) } res = skb ? skb->len :-1 ;if (total) -udp_rmem_release (sk, total,1 ); +udp_rmem_release (sk, total,1 ,0 );spin_unlock_bh (&rcvq->lock);return res; }
@@ -1471,16 +1483,20 @@ struct sk_buff *__skb_recv_udp(struct sock *sk, unsigned int flags, goto busy_check; } -/* refill the reader queue and walk it again */ +/* refill the reader queue and walk it again +* keep both queues locked to avoid re-acquiring +* the sk_receive_queue lock if fwd memory scheduling +* is needed. +*/ _off = *off;spin_lock (&sk_queue->lock);skb_queue_splice_tail_init (sk_queue, queue); -spin_unlock (&sk_queue->lock); skb =__skb_try_recv_from_queue (sk, queue, flags, - udp_skb_destructor, + udp_skb_dtor_locked, peeked, &_off, err, &last); +spin_unlock (&sk_queue->lock);spin_unlock_bh (&queue->lock);if (skb) { *off = _off;
Signature · 1 line
2.9.3
Quoted 8 lines · Paolo Abeni
This patch series implement an idea suggested by Eric Dumazet to reduce the contention of the udp
sk_receive_queuelock when the socket is under flood.An ancillary queue is added to the udp socket, and the socket always tries first to read packets from such queue. If it's empty, we splice the content from
sk_receive_queueinto the ancillary queue.
Quoted 7 lines
The first patch introduces some helpers to keep the udp code small, and the following two implement the ancillary queue strategy. The code is split to hopefully help the reviewing process.
The measured overall gain under udp flood is in the 20-35% range depending on the numa layout and the number of ingress queue used by the relevant nic.
Certainly sounds good, but can you give real reproducible performance numbers including the test that was run?
Tom
Quoted 23 lines
On a single numa node host, the peak tput is now reached when the traffic targeting the udp socket uses multiple nic rx queues, while on current net-next the tput always decreases when moving from a single rx queue to multiple ones.
Paolo Abeni (3):
net/sock: factor out dequeue/peek with offset code
udp: use a separate rx queue for packet reception
udp: keep thesk_receive_queueheld when splicinginclude/linux/skbuff.h | 7 +++ include/linux/udp.h | 3 + include/net/sock.h | 4 +- include/net/udp.h | 9 +-- include/net/udplite.h | 2 +- net/core/datagram.c | 90 +++++++++++++++------------ net/ipv4/udp.c | 162 +++++++++++++++++++++++++++++++++++++++++++------ net/ipv6/udp.c | 3 +- 8 files changed, 211 insertions(+), 69 deletions(-)--
2.9.3
Paolo Abeni
maintainerQuoted 13 lines · Tom Herbert
Quoted 12 lines · Paolo Abeni
This patch series implement an idea suggested by Eric Dumazet to reduce the contention of the udp
sk_receive_queuelock when the socket is under flood.An ancillary queue is added to the udp socket, and the socket always tries first to read packets from such queue. If it's empty, we splice the content from
sk_receive_queueinto the ancillary queue.The first patch introduces some helpers to keep the udp code small, and the following two implement the ancillary queue strategy. The code is split to hopefully help the reviewing process.
Quoted 6 lines
Quoted 3 lines
The measured overall gain under udp flood is in the 20-35% range depending on the numa layout and the number of ingress queue used by the relevant nic.
Certainly sounds good, but can you give real reproducible performance numbers including the test that was run?
You are right, and I'm sorry, the cover letter was too terse.
I used pktgen as sender, with 64 bytes packets, random src port on an host b2b connected via a 10Gbs link with the dut.
On the receiver I used the udp_sink program by Jesper (https://github.com/netoptimizer/network-testing/blob/master/src/udp_sink.c) and I configured an h/w l4 rx hash, so that I could control the number of ingress nic rx queues hit by the udp traffic via ethtool -L.
The udp_sink program was bound to the first idle cpu, to get more stable numbers.
Using a single numa note as receiver, I got the following:
nic rx queues vanilla patched kernel
1 1820 kpps 1900 kpps
2 1950 kpps 2500 kpps
16 1670 kpps 2120 kpps
When using a single nic rx queue I also enabled busy polling; elsewhere, in my scenario, the bh processing becames the bottle-neck and this produces large artifacts in the measured performances (e.g. improving the udp sink run time, decreases the overall tput, since more action from the scheduler comes into play).
Cheers,
Paolo