aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv4/tcp_ipv4.c
diff options
context:
space:
mode:
authorEric Dumazet <edumazet@google.com>2019-03-22 11:56:40 -0400
committerDavid S. Miller <davem@davemloft.net>2019-03-23 21:57:38 -0400
commit8b27dae5a2e89a61c46c6dbc76c040c0e6d0ed4c (patch)
tree0e6f2cfd66715d2234acda3ae48d1543facc5303 /net/ipv4/tcp_ipv4.c
parent472c2e07eef045145bc1493cc94a01c87140780a (diff)
tcp: add one skb cache for rx
Often times, recvmsg() system calls and BH handling for a particular TCP socket are done on different cpus. This means the incoming skb had to be allocated on a cpu, but freed on another. This incurs a high spinlock contention in slab layer for small rpc, but also a high number of cache line ping pongs for larger packets. A full size GRO packet might use 45 page fragments, meaning that up to 45 put_page() can be involved. More over performing the __kfree_skb() in the recvmsg() context adds a latency for user applications, and increase probability of trapping them in backlog processing, since the BH handler might found the socket owned by the user. This patch, combined with the prior one increases the rpc performance by about 10 % on servers with large number of cores. (tcp_rr workload with 10,000 flows and 112 threads reach 9 Mpps instead of 8 Mpps) This also increases single bulk flow performance on 40Gbit+ links, since in this case there are often two cpus working in tandem : - CPU handling the NIC rx interrupts, feeding the receive queue, and (after this patch) freeing the skbs that were consumed. - CPU in recvmsg() system call, essentially 100 % busy copying out data to user space. Having at most one skb in a per-socket cache has very little risk of memory exhaustion, and since it is protected by socket lock, its management is essentially free. Note that if rps/rfs is used, we do not enable this feature, because there is high chance that the same cpu is handling both the recvmsg() system call and the TCP rx path, but that another cpu did the skb allocations in the device driver right before the RPS/RFS logic. To properly handle this case, it seems we would need to record on which cpu skb was allocated, and use a different channel to give skbs back to this cpu. Signed-off-by: Eric Dumazet <edumazet@google.com> Acked-by: Soheil Hassas Yeganeh <soheil@google.com> Acked-by: Willem de Bruijn <willemb@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv4/tcp_ipv4.c')
-rw-r--r--net/ipv4/tcp_ipv4.c11
1 files changed, 9 insertions, 2 deletions
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 277d71239d75..3979939804b7 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1774,6 +1774,7 @@ static void tcp_v4_fill_cb(struct sk_buff *skb, const struct iphdr *iph,
1774int tcp_v4_rcv(struct sk_buff *skb) 1774int tcp_v4_rcv(struct sk_buff *skb)
1775{ 1775{
1776 struct net *net = dev_net(skb->dev); 1776 struct net *net = dev_net(skb->dev);
1777 struct sk_buff *skb_to_free;
1777 int sdif = inet_sdif(skb); 1778 int sdif = inet_sdif(skb);
1778 const struct iphdr *iph; 1779 const struct iphdr *iph;
1779 const struct tcphdr *th; 1780 const struct tcphdr *th;
@@ -1905,11 +1906,17 @@ process:
1905 tcp_segs_in(tcp_sk(sk), skb); 1906 tcp_segs_in(tcp_sk(sk), skb);
1906 ret = 0; 1907 ret = 0;
1907 if (!sock_owned_by_user(sk)) { 1908 if (!sock_owned_by_user(sk)) {
1909 skb_to_free = sk->sk_rx_skb_cache;
1910 sk->sk_rx_skb_cache = NULL;
1908 ret = tcp_v4_do_rcv(sk, skb); 1911 ret = tcp_v4_do_rcv(sk, skb);
1909 } else if (tcp_add_backlog(sk, skb)) { 1912 } else {
1910 goto discard_and_relse; 1913 if (tcp_add_backlog(sk, skb))
1914 goto discard_and_relse;
1915 skb_to_free = NULL;
1911 } 1916 }
1912 bh_unlock_sock(sk); 1917 bh_unlock_sock(sk);
1918 if (skb_to_free)
1919 __kfree_skb(skb_to_free);
1913 1920
1914put_and_return: 1921put_and_return:
1915 if (refcounted) 1922 if (refcounted)