Message ID | 20230323102649.764958589@linutronix.de |
---|---|
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:604a:0:0:0:0:0 with SMTP id j10csp3138939wrt; Thu, 23 Mar 2023 14:19:03 -0700 (PDT) X-Google-Smtp-Source: AKy350ZLF7Uh05vwzBlVsQwRqnHbQzNvufTxaKFItBlwcK9LbrjBlTsC6XXNutBYYMNCZ74yKFt8 X-Received: by 2002:a17:902:ec82:b0:1a1:8d4e:a71d with SMTP id x2-20020a170902ec8200b001a18d4ea71dmr322912plg.46.1679606343173; Thu, 23 Mar 2023 14:19:03 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1679606343; cv=none; d=google.com; s=arc-20160816; b=lAiALk/RvGG0g5UOeqP5FBDJxPB4YProI+oRcezRK1aMZfeyrGwsUPjyPU5tkpkSuz NXtq5YuxF6gVZXFbw3qJTSAbDzmM6FIfqYzwq7Whfs79a+mTaA3ggOtypsqizrWhFGRW eP8A5AkKEPuNBr9fYlimOH+DxcFVr3dQefJ5yig5VdveGsZBz3PX1CO81WfcDKnsU59x pZxrx22ly1oxz9qG9mUmw+3iSyJHrMQDyfwueZwNj5zKCGGMXqFEl4XQXY3uKa0GbIqk zJiQy46D8iNOcdHshE07wDPFID3EcH5M0eW80iY/CuYBPGEd+hyfX6JCl7UV4GghbO9G CaJg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:date:subject:cc:to:from:dkim-signature :dkim-signature:message-id; bh=d7O5vPae/ySCq0lZnzOM8IQfzFcdO1UdN6fef928yOU=; b=ss1fNvudjXEdySYt53eOXc+lT4xqLJVeQbUSU9yJ1Rb+Q/gMK5GXaYvY4SGSD9Qc2Y jwYwleVwPLXSomcc10vG0BsmFB46nXgb8vgzKxI1fjiDeHjokVe/Xj60zYdeoTPyHdB2 KaSmJmUD/HQBjQnBAxJl50CwkDQgGyoe1QFMK7UmF15eb8pGALdmZb7GQs3hnMgqNjbd oL95jPTvQAYXynIBsKuOpha/5YvCxXv4X8e/+As6f92Af4/quMlMlRR1VzUWH+ZrKYKy CXtBv6qUvTI21hIZixAvGLVdpRV5nTY1Tt3safJM78WFwFF6y5Yg9+NLITprNemzT0i0 kG3Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linutronix.de header.s=2020 header.b="1ubtcT/s"; dkim=neutral (no key) header.i=@linutronix.de header.s=2020e; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=linutronix.de Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id i9-20020a17090332c900b001a1a252c4b1si21022344plr.13.2023.03.23.14.18.50; Thu, 23 Mar 2023 14:19:03 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@linutronix.de header.s=2020 header.b="1ubtcT/s"; dkim=neutral (no key) header.i=@linutronix.de header.s=2020e; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=linutronix.de Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231571AbjCWUzd (ORCPT <rfc822;ezelljr.billy@gmail.com> + 99 others); Thu, 23 Mar 2023 16:55:33 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44600 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229499AbjCWUzb (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Thu, 23 Mar 2023 16:55:31 -0400 Received: from galois.linutronix.de (Galois.linutronix.de [IPv6:2a0a:51c0:0:12e:550::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B8D167AB3; Thu, 23 Mar 2023 13:55:29 -0700 (PDT) Message-ID: <20230323102649.764958589@linutronix.de> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1679604928; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc; bh=d7O5vPae/ySCq0lZnzOM8IQfzFcdO1UdN6fef928yOU=; b=1ubtcT/sd9ub44HPY/9/glqwSFS10QWr0G3HGpPnqMUh5L8nCqPjQFd6TbvyldZ5Sk+nq7 0wtV4BvNas466vpftwEJ8ynboycAV0kn8dqZUhMZ1pi0s566ngEmZWlGkLN6FWqj3clFw1 EK83V4AwJ1h73tzTNua9k/+87GglTQJizgOr1MuptE2VOgvf95TfQzm2vFQUpIYSjKoq9I YHST0KK7sorFM3biYMdHYQOGuOELsbwOdnIF50PeY8PNoRc8y2uQRuUv1f3Db0OpLYobP1 sbKqCBGGdQrNrnZhvH7GL9oEKh2q54mH7cNoJ22DDmxUcRAYeMZbegF7uEU57w== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1679604928; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc; bh=d7O5vPae/ySCq0lZnzOM8IQfzFcdO1UdN6fef928yOU=; b=xzIMqkYkM07TqxAZgBK9d3lbCc+RL56OoTFxnOaQRrmOotnSXvWO+uSXdzcwjMWoQTB2/H WL39zdHirGg2xdCg== From: Thomas Gleixner <tglx@linutronix.de> To: LKML <linux-kernel@vger.kernel.org> Cc: Linus Torvalds <torvalds@linuxfoundation.org>, x86@kernel.org, Wangyang Guo <wangyang.guo@intel.com>, Arjan van De Ven <arjan@linux.intel.com>, "David S. Miller" <davem@davemloft.net>, Eric Dumazet <edumazet@google.com>, Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>, netdev@vger.kernel.org, Will Deacon <will@kernel.org>, Peter Zijlstra <peterz@infradead.org>, Boqun Feng <boqun.feng@gmail.com>, Mark Rutland <mark.rutland@arm.com>, Marc Zyngier <maz@kernel.org>, Qiuxu Zhuo <qiuxu.zhuo@intel.com> Subject: [patch V3 0/4] net, refcount: Address dst_entry reference count scalability issues Date: Thu, 23 Mar 2023 21:55:27 +0100 (CET) X-Spam-Status: No, score=-2.5 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_MED,SPF_HELO_NONE,SPF_PASS autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1761194900729607712?= X-GMAIL-MSGID: =?utf-8?q?1761194900729607712?= |
Series |
net, refcount: Address dst_entry reference count scalability issues
|
|
Message
Thomas Gleixner
March 23, 2023, 8:55 p.m. UTC
Hi! This is version 3 of this series. Version 2 can be found here: https://lore.kernel.org/lkml/20230307125358.772287565@linutronix.de Wangyang and Arjan reported a bottleneck in the networking code related to struct dst_entry::__refcnt. Performance tanks massively when concurrency on a dst_entry increases. This happens when there are a large amount of connections to or from the same IP address. The memtier benchmark when run on the same host as memcached amplifies this massively. But even over real network connections this issue can be observed at an obviously smaller scale (due to the network bandwith limitations in my setup, i.e. 1Gb). How to reproduce: Run memcached with -t $N and memtier_benchmark with -t $M and --ratio=1:100 on the same machine. localhost connections amplify the problem. Start with the defaults for $N and $M and increase them. Depending on your machine this will tank at some point. But even in reasonably small $N, $M scenarios the refcount operations and the resulting false sharing fallout becomes visible in perf top. At some point it becomes the dominating issue. There are two factors which make this reference count a scalability issue: 1) False sharing 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. The other problem is struct rtable, which 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 and contribute to the performance problem. 2) atomic_inc_not_zero() A reference on dst_entry:__refcnt is acquired via atomic_inc_not_zero() and released via atomic_dec_return(). atomic_inc_not_zero() is implemted via a atomic_try_cmpxchg() loop, which exposes O(N^2) behaviour under contention with N concurrent operations. Contention scalability is degrading with even a small amount of contenders and gets worse from there. Lightweight instrumentation exposed an average of 8!! retry loops per atomic_inc_not_zero() invocation in a inc()/dec() loop running concurrently on 112 CPUs. There is nothing which can be done to make atomic_inc_not_zero() more scalable. The following series addresses these issues: 1) Reorder and pad struct dst_entry to prevent the false sharing. 2) Implement and use a reference count implementation which avoids the atomic_inc_not_zero() problem. It is slightly less performant in the case of the final 0 -> -1 transition, but the deconstruction of these objects is a low frequency event. get()/put() pairs are in the hotpath and that's what this implementation optimizes for. The algorithm of this reference count is only suitable for RCU managed objects. Therefore it cannot replace the refcount_t algorithm, which is also based on atomic_inc_not_zero(), due to a subtle race condition related to the 0 -> -1 transition and the final verdict to mark the reference count dead. See details in patch 2/3. It might be just my lack of imagination which declares this to be impossible and I'd be happy to be proven wrong. As a bonus the new rcuref implementation provides underflow/overflow detection and mitigation while being performance wise on par with open coded atomic_inc_not_zero() / atomic_dec_return() pairs even in the non-contended case. The combination of these two changes results in performance gains in micro benchmarks and also localhost and networked memtier benchmarks talking to memcached. It's hard to quantify the benchmark results as they depend heavily on the micro-architecture and the number of concurrent operations. The overall gain of both changes for localhost memtier ranges from 1.2X to 3.2X and from +2% to %5% range for networked operations on a 1Gb connection. A micro benchmark which enforces maximized concurrency shows a gain between 1.2X and 4.7X!!! Obviously this is focussed on a particular problem and therefore needs to be discussed in detail. It also requires wider testing outside of the cases which this is focussed on. Though the false sharing issue is obvious and should be addressed independent of the more focussed reference count changes. The series is also available from git: git://git.kernel.org/pub/scm/linux/kernel/git/tglx/devel.git rcuref Changes vs. V2: - Rename __refcnt to __rcuref (Linus) - Fix comments and changelogs (Mark, Qiuxu) - Fixup kernel doc of generated atomic_add_negative() variants I want to say thanks to Wangyang who analyzed the issue and provided the initial fix for the false sharing problem. Further thanks go to Arjan Peter, Marc, Will and Borislav for valuable input and providing test results on machines which I do not have access to, and to Linus and Eric, Qiuxu and Mark for helpful feedback. Thanks, tglx
Comments
I've stuffed the two atomic patches in tip/locking/rcuref for Jakub which I then merged into tip/locking/core. Jakub, you should be able to merge that topic branch (rc1 based) and stuff the network bits on top. If anything went sideways, please holler!
Hello: This series was applied to netdev/net-next.git (main) by Jakub Kicinski <kuba@kernel.org>: On Thu, 23 Mar 2023 21:55:27 +0100 (CET) you wrote: > Hi! > > This is version 3 of this series. Version 2 can be found here: > > https://lore.kernel.org/lkml/20230307125358.772287565@linutronix.de > > Wangyang and Arjan reported a bottleneck in the networking code related to > struct dst_entry::__refcnt. Performance tanks massively when concurrency on > a dst_entry increases. > > [...] Here is the summary with links: - [V3,1/4,V2,1/4] net: dst: Prevent false sharing vs. dst_entry:: __refcnt https://git.kernel.org/netdev/net-next/c/d288a162dd1c - [V3,2/4] atomics: Provide atomic_add_negative() variants https://git.kernel.org/netdev/net-next/c/e5ab9eff46b0 - [V3,3/4] atomics: Provide rcuref - scalable reference counting https://git.kernel.org/netdev/net-next/c/ee1ee6db0779 - [V3,4/4] net: dst: Switch to rcuref_t reference counting https://git.kernel.org/netdev/net-next/c/bc9d3a9f2afc You are awesome, thank you!
On Thu, Mar 23, 2023 at 09:55:27PM +0100, Thomas Gleixner wrote: > Hi! > > This is version 3 of this series. Version 2 can be found here: > > https://lore.kernel.org/lkml/20230307125358.772287565@linutronix.de > > Wangyang and Arjan reported a bottleneck in the networking code related to > struct dst_entry::__refcnt. Performance tanks massively when concurrency on > a dst_entry increases. > > This happens when there are a large amount of connections to or from the > same IP address. The memtier benchmark when run on the same host as > memcached amplifies this massively. But even over real network connections > this issue can be observed at an obviously smaller scale (due to the > network bandwith limitations in my setup, i.e. 1Gb). How to reproduce: > > Run memcached with -t $N and memtier_benchmark with -t $M and --ratio=1:100 > on the same machine. localhost connections amplify the problem. > > Start with the defaults for $N and $M and increase them. Depending on > your machine this will tank at some point. But even in reasonably small > $N, $M scenarios the refcount operations and the resulting false sharing > fallout becomes visible in perf top. At some point it becomes the > dominating issue. > > There are two factors which make this reference count a scalability issue: > > 1) False sharing > > 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. > > The other problem is struct rtable, which 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 and contribute to the performance > problem. > > 2) atomic_inc_not_zero() > > A reference on dst_entry:__refcnt is acquired via > atomic_inc_not_zero() and released via atomic_dec_return(). > > atomic_inc_not_zero() is implemted via a atomic_try_cmpxchg() loop, > which exposes O(N^2) behaviour under contention with N concurrent > operations. Contention scalability is degrading with even a small > amount of contenders and gets worse from there. > > Lightweight instrumentation exposed an average of 8!! retry loops per > atomic_inc_not_zero() invocation in a inc()/dec() loop running > concurrently on 112 CPUs. Huh. 8 is pretty bad, 8! far worse, but 8!!? 3.4e168186??? (Sorry, couldn't resist...) > There is nothing which can be done to make atomic_inc_not_zero() more > scalable. > > The following series addresses these issues: > > 1) Reorder and pad struct dst_entry to prevent the false sharing. > > 2) Implement and use a reference count implementation which avoids the > atomic_inc_not_zero() problem. > > It is slightly less performant in the case of the final 0 -> -1 > transition, but the deconstruction of these objects is a low > frequency event. get()/put() pairs are in the hotpath and that's > what this implementation optimizes for. > > The algorithm of this reference count is only suitable for RCU > managed objects. Therefore it cannot replace the refcount_t > algorithm, which is also based on atomic_inc_not_zero(), due to a > subtle race condition related to the 0 -> -1 transition and the final > verdict to mark the reference count dead. See details in patch 2/3. > > It might be just my lack of imagination which declares this to be > impossible and I'd be happy to be proven wrong. It is possible to make something like rcuref_get that does only a READ_ONCE(), WRITE_ONCE() to storage local to the task, smp_mb(), READ_ONCE() and compare in the common case. There would be something like rcuref_put() that did an smp_store_release() to the same storage local to the task. Of course, there is always a catch, and here there are several: 1. Instead of just returning a pointer to a struct dst_entry, sk_dst_get() would need to hand back both that pointer along with a pointer to the aforementioned storage local to the task. 2. A generally useful implementation would require the counterpart to rcuref_get() to sometimes allocate memory. Failure to allocate memory would imply failure to gain a reference. 3. The structure would not need to be RCU-freed, but it still would need to be freed specially. (All of the non-NULL locations in all the storage local to the tasks needs to be checked, but batching optimizations work well here.) 4. The smp_mb() compares well to the atomic_add_negative_relaxed(), but getting rid of that smp_mb() either adds more RCU-like delays on the free path on the one hand, or requires IPIs on the free path on the other. 5. The aforementioned storage local to the task could instead be local to the CPU, but at the expense of some put-side cache misses and possible false sharing in the case where rcuref_get() runs on one CPU and rcuref_put() runs on another. Of course, the current rcuref_put() gets that already due to the use of atomic_add_negative_release() on shared memory, so perhaps not a big deal. Not sure it is worth it, but you did ask! If this does turn out to be an attractive option, please let me know. Thanx, Paul > As a bonus the new rcuref implementation provides underflow/overflow > detection and mitigation while being performance wise on par with > open coded atomic_inc_not_zero() / atomic_dec_return() pairs even in > the non-contended case. > > The combination of these two changes results in performance gains in micro > benchmarks and also localhost and networked memtier benchmarks talking to > memcached. It's hard to quantify the benchmark results as they depend > heavily on the micro-architecture and the number of concurrent operations. > > The overall gain of both changes for localhost memtier ranges from 1.2X to > 3.2X and from +2% to %5% range for networked operations on a 1Gb connection. > > A micro benchmark which enforces maximized concurrency shows a gain between > 1.2X and 4.7X!!! > > Obviously this is focussed on a particular problem and therefore needs to > be discussed in detail. It also requires wider testing outside of the cases > which this is focussed on. > > Though the false sharing issue is obvious and should be addressed > independent of the more focussed reference count changes. > > The series is also available from git: > > git://git.kernel.org/pub/scm/linux/kernel/git/tglx/devel.git rcuref > > Changes vs. V2: > > - Rename __refcnt to __rcuref (Linus) > > - Fix comments and changelogs (Mark, Qiuxu) > > - Fixup kernel doc of generated atomic_add_negative() variants > > I want to say thanks to Wangyang who analyzed the issue and provided the > initial fix for the false sharing problem. Further thanks go to Arjan > Peter, Marc, Will and Borislav for valuable input and providing test > results on machines which I do not have access to, and to Linus and > Eric, Qiuxu and Mark for helpful feedback. > > Thanks, > > tglx >
On Thu, Mar 23, 2023 at 09:55:27PM +0100, Thomas Gleixner wrote: > Hi! > > This is version 3 of this series. Version 2 can be found here: > > https://lore.kernel.org/lkml/20230307125358.772287565@linutronix.de Hi, I want to raise your attention to this bug report from Intel. https://lore.kernel.org/all/202304162125.18b7bcdd-oliver.sang@intel.com/ We (Nvidia) are experiencing similar failures in our regressions too. Revert of last two patches [1] from this series removed the panics, but didn't add confidence due to another (???) netdev failure: [ +10.080020] unregister_netdevice: waiting for eth3 to become free. Usage count = 2 Thanks [1] bc9d3a9f2afc net: dst: Switch to rcuref_t reference counting d288a162dd1c net: dst: Prevent false sharing vs. dst_entry:: __refcnt