[PATCH v3 net-next] net: neigh: avoid calling neigh_forced_gc on every alloc when table is full
From: Vimal Agrawal <hidden>
Date: 2026-07-14 13:39:39
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Once the neighbour table exceeds gc_thresh3, neigh_forced_gc() is called on every allocation attempt with no rate limiting. In workloads with mostly active/reachable entries, the GC walk traverses a large portion of the neighbour table without reclaiming entries, holding tbl->lock for an extended period. This causes severe lock contention and allocation latencies exceeding 16ms under sustained neighbour creation. Add a pre-lock check in neigh_forced_gc() to skip the GC run if one was performed within the last 50 ms, but only when gc_thresh3 is configured at or above NEIGH_FORCED_GC_LARGE_TABLE_THRESH (16384). This avoids repeated full table scans and lock acquisitions on the hot allocation path while leaving default-sized and test deployments (gc_thresh3 < 16384) completely unaffected. Profiling of neigh_create() shows ~3 orders of magnitude latency improvement with this change. Link: https://lore.kernel.org/netdev/CALkUMdSCpx_ywYCx_ePLdm6yioO1nQWx7sSM=AEgsq0kywHxTw@mail.gmail.com/ (local) Signed-off-by: Vimal Agrawal <redacted> --- Thank you for catching this. The v2 rate limit was unconditional, so it fired even in the selftest which sets gc_thresh3=10, preventing forced_gc_runs from being incremented. v3 restricts rate limiting to tables where gc_thresh3 >= 16384. Selftests and default deployments (gc_thresh3=1024) are unaffected. v3: Restrict rate limiting to tables with gc_thresh3 >= 16384 to avoid breaking selftests and default deployments (gc_thresh3=1024). v2: Changed rate-limit window from 1s (HZ) to 50ms (msecs_to_jiffies(50)) based on profiling data showing 44% -> 2.56% CPU reduction. net/core/neighbour.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/net/core/neighbour.c b/net/core/neighbour.c
index 1349c0eed..9438c5821 100644
--- a/net/core/neighbour.c
+++ b/net/core/neighbour.c@@ -250,6 +250,8 @@ bool neigh_remove_one(struct neighbour *n) return retval; } +#define NEIGH_FORCED_GC_LARGE_TABLE_THRESH 16384 + static int neigh_forced_gc(struct neigh_table *tbl) { int max_clean = atomic_read(&tbl->gc_entries) -
@@ -260,6 +262,15 @@ static int neigh_forced_gc(struct neigh_table *tbl) int shrunk = 0; int loop = 0; + /* + * For large neighbor tables, repeated forced GC passes can spend + * significant CPU scanning neighbor entries when most remain active. + * Rate-limit consecutive forced GC passes to reduce CPU overhead. + */ + if (READ_ONCE(tbl->gc_thresh3) >= NEIGH_FORCED_GC_LARGE_TABLE_THRESH && + time_before(jiffies, READ_ONCE(tbl->last_flush) + msecs_to_jiffies(50))) + return 0; + NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs); spin_lock_bh(&tbl->lock);
--
2.43.0