Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: dst: Prevent false sharing vs. dst_entry:: __refcnt

dst_entry::__refcnt is highly contended in scenarios where many connections
happen from and to the same IP. The reference count is an atomic_t, so the
reference count operations have to take the cache-line exclusive.

Aside of the unavoidable reference count contention there is another
significant problem which is caused by that: False sharing.

perf top identified two affected read accesses. dst_entry::lwtstate and
rtable::rt_genid.

dst_entry:__refcnt is located at offset 64 of dst_entry, which puts it into
a seperate cacheline vs. the read mostly members located at the beginning
of the struct.

That prevents false sharing vs. the struct members in the first 64
bytes of the structure, but there is also

dst_entry::lwtstate

which is located after the reference count and in the same cache line. This
member is read after a reference count has been acquired.

struct rtable embeds a struct dst_entry at offset 0. struct dst_entry has a
size of 112 bytes, which means that the struct members of rtable which
follow the dst member share the same cache line as dst_entry::__refcnt.
Especially

rtable::rt_genid

is also read by the contexts which have a reference count acquired
already.

When dst_entry:__refcnt is incremented or decremented via an atomic
operation these read accesses stall. This was found when analysing the
memtier benchmark in 1:100 mode, which amplifies the problem extremly.

Move the rt[6i]_uncached[_list] members out of struct rtable and struct
rt6_info into struct dst_entry to provide padding and move the lwtstate
member after that so it ends up in the same cache line.

The resulting improvement depends on the micro-architecture and the number
of CPUs. It ranges from +20% to +120% with a localhost memtier/memcached
benchmark.

[ tglx: Rearrange struct ]

Signed-off-by: Wangyang Guo <wangyang.guo@intel.com>
Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://lore.kernel.org/r/20230323102800.042297517@linutronix.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Wangyang Guo and committed by
Jakub Kicinski
d288a162 b133fffe

+42 -35
+14 -1
include/net/dst.h
··· 69 69 #endif 70 70 int __use; 71 71 unsigned long lastuse; 72 - struct lwtunnel_state *lwtstate; 73 72 struct rcu_head rcu_head; 74 73 short error; 75 74 short __pad; 76 75 __u32 tclassid; 77 76 #ifndef CONFIG_64BIT 77 + struct lwtunnel_state *lwtstate; 78 78 atomic_t __refcnt; /* 32-bit offset 64 */ 79 79 #endif 80 80 netdevice_tracker dev_tracker; 81 + 82 + /* 83 + * Used by rtable and rt6_info. Moves lwtstate into the next cache 84 + * line on 64bit so that lwtstate does not cause false sharing with 85 + * __refcnt under contention of __refcnt. This also puts the 86 + * frequently accessed members of rtable and rt6_info out of the 87 + * __refcnt cache line. 88 + */ 89 + struct list_head rt_uncached; 90 + struct uncached_list *rt_uncached_list; 91 + #ifdef CONFIG_64BIT 92 + struct lwtunnel_state *lwtstate; 93 + #endif 81 94 }; 82 95 83 96 struct dst_metrics {
-3
include/net/ip6_fib.h
··· 217 217 struct inet6_dev *rt6i_idev; 218 218 u32 rt6i_flags; 219 219 220 - struct list_head rt6i_uncached; 221 - struct uncached_list *rt6i_uncached_list; 222 - 223 220 /* more non-fragment space at head required */ 224 221 unsigned short rt6i_nfheader_len; 225 222 };
+1 -1
include/net/ip6_route.h
··· 100 100 static inline void ip6_rt_put_flags(struct rt6_info *rt, int flags) 101 101 { 102 102 if (!(flags & RT6_LOOKUP_F_DST_NOREF) || 103 - !list_empty(&rt->rt6i_uncached)) 103 + !list_empty(&rt->dst.rt_uncached)) 104 104 ip6_rt_put(rt); 105 105 } 106 106
-3
include/net/route.h
··· 78 78 /* Miscellaneous cached information */ 79 79 u32 rt_mtu_locked:1, 80 80 rt_pmtu:31; 81 - 82 - struct list_head rt_uncached; 83 - struct uncached_list *rt_uncached_list; 84 81 }; 85 82 86 83 static inline bool rt_is_input_route(const struct rtable *rt)
+10 -10
net/ipv4/route.c
··· 1508 1508 { 1509 1509 struct uncached_list *ul = raw_cpu_ptr(&rt_uncached_list); 1510 1510 1511 - rt->rt_uncached_list = ul; 1511 + rt->dst.rt_uncached_list = ul; 1512 1512 1513 1513 spin_lock_bh(&ul->lock); 1514 - list_add_tail(&rt->rt_uncached, &ul->head); 1514 + list_add_tail(&rt->dst.rt_uncached, &ul->head); 1515 1515 spin_unlock_bh(&ul->lock); 1516 1516 } 1517 1517 1518 1518 void rt_del_uncached_list(struct rtable *rt) 1519 1519 { 1520 - if (!list_empty(&rt->rt_uncached)) { 1521 - struct uncached_list *ul = rt->rt_uncached_list; 1520 + if (!list_empty(&rt->dst.rt_uncached)) { 1521 + struct uncached_list *ul = rt->dst.rt_uncached_list; 1522 1522 1523 1523 spin_lock_bh(&ul->lock); 1524 - list_del_init(&rt->rt_uncached); 1524 + list_del_init(&rt->dst.rt_uncached); 1525 1525 spin_unlock_bh(&ul->lock); 1526 1526 } 1527 1527 } ··· 1546 1546 continue; 1547 1547 1548 1548 spin_lock_bh(&ul->lock); 1549 - list_for_each_entry_safe(rt, safe, &ul->head, rt_uncached) { 1549 + list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) { 1550 1550 if (rt->dst.dev != dev) 1551 1551 continue; 1552 1552 rt->dst.dev = blackhole_netdev; 1553 1553 netdev_ref_replace(dev, blackhole_netdev, 1554 1554 &rt->dst.dev_tracker, GFP_ATOMIC); 1555 - list_move(&rt->rt_uncached, &ul->quarantine); 1555 + list_move(&rt->dst.rt_uncached, &ul->quarantine); 1556 1556 } 1557 1557 spin_unlock_bh(&ul->lock); 1558 1558 } ··· 1644 1644 rt->rt_uses_gateway = 0; 1645 1645 rt->rt_gw_family = 0; 1646 1646 rt->rt_gw4 = 0; 1647 - INIT_LIST_HEAD(&rt->rt_uncached); 1647 + INIT_LIST_HEAD(&rt->dst.rt_uncached); 1648 1648 1649 1649 rt->dst.output = ip_output; 1650 1650 if (flags & RTCF_LOCAL) ··· 1675 1675 new_rt->rt_gw4 = rt->rt_gw4; 1676 1676 else if (rt->rt_gw_family == AF_INET6) 1677 1677 new_rt->rt_gw6 = rt->rt_gw6; 1678 - INIT_LIST_HEAD(&new_rt->rt_uncached); 1678 + INIT_LIST_HEAD(&new_rt->dst.rt_uncached); 1679 1679 1680 1680 new_rt->dst.input = rt->dst.input; 1681 1681 new_rt->dst.output = rt->dst.output; ··· 2859 2859 else if (rt->rt_gw_family == AF_INET6) 2860 2860 rt->rt_gw6 = ort->rt_gw6; 2861 2861 2862 - INIT_LIST_HEAD(&rt->rt_uncached); 2862 + INIT_LIST_HEAD(&rt->dst.rt_uncached); 2863 2863 } 2864 2864 2865 2865 dst_release(dst_orig);
+2 -2
net/ipv4/xfrm4_policy.c
··· 91 91 xdst->u.rt.rt_gw6 = rt->rt_gw6; 92 92 xdst->u.rt.rt_pmtu = rt->rt_pmtu; 93 93 xdst->u.rt.rt_mtu_locked = rt->rt_mtu_locked; 94 - INIT_LIST_HEAD(&xdst->u.rt.rt_uncached); 94 + INIT_LIST_HEAD(&xdst->u.rt.dst.rt_uncached); 95 95 rt_add_uncached_list(&xdst->u.rt); 96 96 97 97 return 0; ··· 121 121 struct xfrm_dst *xdst = (struct xfrm_dst *)dst; 122 122 123 123 dst_destroy_metrics_generic(dst); 124 - if (xdst->u.rt.rt_uncached_list) 124 + if (xdst->u.rt.dst.rt_uncached_list) 125 125 rt_del_uncached_list(&xdst->u.rt); 126 126 xfrm_dst_destroy(xdst); 127 127 }
+13 -13
net/ipv6/route.c
··· 139 139 { 140 140 struct uncached_list *ul = raw_cpu_ptr(&rt6_uncached_list); 141 141 142 - rt->rt6i_uncached_list = ul; 142 + rt->dst.rt_uncached_list = ul; 143 143 144 144 spin_lock_bh(&ul->lock); 145 - list_add_tail(&rt->rt6i_uncached, &ul->head); 145 + list_add_tail(&rt->dst.rt_uncached, &ul->head); 146 146 spin_unlock_bh(&ul->lock); 147 147 } 148 148 149 149 void rt6_uncached_list_del(struct rt6_info *rt) 150 150 { 151 - if (!list_empty(&rt->rt6i_uncached)) { 152 - struct uncached_list *ul = rt->rt6i_uncached_list; 151 + if (!list_empty(&rt->dst.rt_uncached)) { 152 + struct uncached_list *ul = rt->dst.rt_uncached_list; 153 153 154 154 spin_lock_bh(&ul->lock); 155 - list_del_init(&rt->rt6i_uncached); 155 + list_del_init(&rt->dst.rt_uncached); 156 156 spin_unlock_bh(&ul->lock); 157 157 } 158 158 } ··· 169 169 continue; 170 170 171 171 spin_lock_bh(&ul->lock); 172 - list_for_each_entry_safe(rt, safe, &ul->head, rt6i_uncached) { 172 + list_for_each_entry_safe(rt, safe, &ul->head, dst.rt_uncached) { 173 173 struct inet6_dev *rt_idev = rt->rt6i_idev; 174 174 struct net_device *rt_dev = rt->dst.dev; 175 175 bool handled = false; ··· 188 188 handled = true; 189 189 } 190 190 if (handled) 191 - list_move(&rt->rt6i_uncached, 191 + list_move(&rt->dst.rt_uncached, 192 192 &ul->quarantine); 193 193 } 194 194 spin_unlock_bh(&ul->lock); ··· 334 334 static void rt6_info_init(struct rt6_info *rt) 335 335 { 336 336 memset_after(rt, 0, dst); 337 - INIT_LIST_HEAD(&rt->rt6i_uncached); 337 + INIT_LIST_HEAD(&rt->dst.rt_uncached); 338 338 } 339 339 340 340 /* allocate dst with ip6_dst_ops */ ··· 2638 2638 dst = ip6_route_output_flags_noref(net, sk, fl6, flags); 2639 2639 rt6 = (struct rt6_info *)dst; 2640 2640 /* For dst cached in uncached_list, refcnt is already taken. */ 2641 - if (list_empty(&rt6->rt6i_uncached) && !dst_hold_safe(dst)) { 2641 + if (list_empty(&rt6->dst.rt_uncached) && !dst_hold_safe(dst)) { 2642 2642 dst = &net->ipv6.ip6_null_entry->dst; 2643 2643 dst_hold(dst); 2644 2644 } ··· 2748 2748 from = rcu_dereference(rt->from); 2749 2749 2750 2750 if (from && (rt->rt6i_flags & RTF_PCPU || 2751 - unlikely(!list_empty(&rt->rt6i_uncached)))) 2751 + unlikely(!list_empty(&rt->dst.rt_uncached)))) 2752 2752 dst_ret = rt6_dst_from_check(rt, from, cookie); 2753 2753 else 2754 2754 dst_ret = rt6_check(rt, from, cookie); ··· 6477 6477 net->ipv6.ip6_null_entry->dst.ops = &net->ipv6.ip6_dst_ops; 6478 6478 dst_init_metrics(&net->ipv6.ip6_null_entry->dst, 6479 6479 ip6_template_metrics, true); 6480 - INIT_LIST_HEAD(&net->ipv6.ip6_null_entry->rt6i_uncached); 6480 + INIT_LIST_HEAD(&net->ipv6.ip6_null_entry->dst.rt_uncached); 6481 6481 6482 6482 #ifdef CONFIG_IPV6_MULTIPLE_TABLES 6483 6483 net->ipv6.fib6_has_custom_rules = false; ··· 6489 6489 net->ipv6.ip6_prohibit_entry->dst.ops = &net->ipv6.ip6_dst_ops; 6490 6490 dst_init_metrics(&net->ipv6.ip6_prohibit_entry->dst, 6491 6491 ip6_template_metrics, true); 6492 - INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->rt6i_uncached); 6492 + INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->dst.rt_uncached); 6493 6493 6494 6494 net->ipv6.ip6_blk_hole_entry = kmemdup(&ip6_blk_hole_entry_template, 6495 6495 sizeof(*net->ipv6.ip6_blk_hole_entry), ··· 6499 6499 net->ipv6.ip6_blk_hole_entry->dst.ops = &net->ipv6.ip6_dst_ops; 6500 6500 dst_init_metrics(&net->ipv6.ip6_blk_hole_entry->dst, 6501 6501 ip6_template_metrics, true); 6502 - INIT_LIST_HEAD(&net->ipv6.ip6_blk_hole_entry->rt6i_uncached); 6502 + INIT_LIST_HEAD(&net->ipv6.ip6_blk_hole_entry->dst.rt_uncached); 6503 6503 #ifdef CONFIG_IPV6_SUBTREES 6504 6504 net->ipv6.fib6_routes_require_src = 0; 6505 6505 #endif
+2 -2
net/ipv6/xfrm6_policy.c
··· 89 89 xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway; 90 90 xdst->u.rt6.rt6i_dst = rt->rt6i_dst; 91 91 xdst->u.rt6.rt6i_src = rt->rt6i_src; 92 - INIT_LIST_HEAD(&xdst->u.rt6.rt6i_uncached); 92 + INIT_LIST_HEAD(&xdst->u.rt6.dst.rt_uncached); 93 93 rt6_uncached_list_add(&xdst->u.rt6); 94 94 95 95 return 0; ··· 121 121 if (likely(xdst->u.rt6.rt6i_idev)) 122 122 in6_dev_put(xdst->u.rt6.rt6i_idev); 123 123 dst_destroy_metrics_generic(dst); 124 - if (xdst->u.rt6.rt6i_uncached_list) 124 + if (xdst->u.rt6.dst.rt_uncached_list) 125 125 rt6_uncached_list_del(&xdst->u.rt6); 126 126 xfrm_dst_destroy(xdst); 127 127 }