From: Kuniyuki Iwashima <kuniyu@google.com>
To: Andrew Morton <akpm@linux-foundation.org>,
Johannes Weiner <hannes@cmpxchg.org>,
Michal Hocko <mhocko@kernel.org>,
Roman Gushchin <roman.gushchin@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>
Cc: Muchun Song <muchun.song@linux.dev>,
Kuniyuki Iwashima <kuniyu@google.com>,
Kuniyuki Iwashima <kuni1840@gmail.com>,
linux-mm@kvack.org, cgroups@vger.kernel.org
Subject: [PATCH v3 4/4] memcg: Uncharge pages outside memcg_stock.lock in refill_stock().
Date: Thu, 29 Oct 2026 19:44:15 +0000 [thread overview]
Message-ID: <20261029194452.3189365-5-kuniyu@google.com> (raw)
In-Reply-To: <20261029194452.3189365-1-kuniyu@google.com>
On host w/ AMD EPYC 9B45 128-Core Processor (Zen 5 Turin,
2 sockets, 512 vCPUs), tcp_rr w/ 20000 flows throughput often
degrades by ~50%, 32M -> 17M~27M.
In the bad case, the number of involuntary context switches
was extremely high by 12x (241733 -> 3093367).
In perf record, 96% samples had the same pattern, where the
thread was interrupted during page_counter_uncharge() by softirq,
and later ksoftirqd was woken up:
# perf record -C 0-15 -e sched:sched_wakeup -g \
-o /tmp/sched_wakeup.data -- sleep 1
# perf report -i /tmp/sched_wakeup.data --stdio --no-children
96.36% tcp_rr [kernel.kallsyms] [k] wake_up_process
|
---wake_up_process
|
--96.00%--irq_exit_rcu
|
--95.82%--sysvec_call_function_single
asm_sysvec_call_function_single
|
--95.58%--page_counter_uncharge
mem_cgroup_sk_uncharge
sock_rfree
tcp_recvmsg
__x64_sys_recvfrom
do_syscall_64
entry_SYSCALL_64_after_hwframe
# perf script -i /tmp/sched_wakeup.data | grep -A 20 "comm=ksoftirqd"
tcp_rr 101952 [001] 9679.340985: sched:sched_wakeup: comm=ksoftirqd/1 pid=23 prio=120 target_cpu=001
ffffffffb64cd0cc wake_up_process+0xe6c ([kernel.kallsyms])
ffffffffb64cb034 irq_exit_rcu+0x264 ([kernel.kallsyms])
ffffffffb68ad31c sysvec_call_function_single+0xcc ([kernel.kallsyms])
ffffffffb560158a asm_sysvec_call_function_single+0x1a ([kernel.kallsyms])
ffffffffb653c4ce page_counter_uncharge+0x2e ([kernel.kallsyms])
ffffffffb6541b28 mem_cgroup_sk_uncharge+0x3c8 ([kernel.kallsyms])
ffffffffb6561383 sock_rfree+0x83 ([kernel.kallsyms])
ffffffffb6577faf tcp_recvmsg+0x2ff ([kernel.kallsyms])
ffffffffb65604d9 __x64_sys_recvfrom+0x159 ([kernel.kallsyms])
The problem is that when stock_pages exceeds MEMCG_STOCK_HIGH
or all slots in struct memcg_stock_pcp are full, refill_stock()
calls memcg_uncharge() or drain_stock() while holding
memcg_stock.lock.
Because local_trylock() only disables preemption and leaves
interrupts and softirqs enabled, an RPS IPI arriving while
refill_stock() stalls on page_counter_uncharge() invokes
NET_RX_SOFTIRQ (process_backlog()) on the same CPU while
memcg_stock.lock is held.
Every incoming packet in process_backlog() then fails
local_trylock() in consume_stock() and falls back to
try_charge_memcg() and refill_stock(), hammering the global
page_counter atomic twice per packet, and slow processing
finally wakes up ksoftirqd.
Let's move memcg_uncharge() and css_put() outside
local_unlock(&memcg_stock.lock) in refill_stock().
With the change, the flakiness disappeared on the 512 vCPU platform.
In addition, on machine w/ AMD EPYC 7B12 64-Core Processor (Zen 2
Rome, 2 sockets, 256 vCPU), tcp_rr w/ 10000 flows achieves 36% more
throughput with lower latency.
tcp_rr (10000 flows, 128 threads, 60s, average of 3 runs):
Metric | Baseline | Patched | Diff
---------------------------+----------------+------------+--------
Throughput (RPS) | 20,884,499 | 28,453,051 | +36.24%
Latency Mean (us) | 478.46 | 351.07 | -26.63%
Latency p50 (us) | 453.96 | 343.04 | -24.43%
Latency p90 (us) | 669.00 | 479.57 | -28.32%
Latency p99 (us) | 969.38 | 593.92 | -38.73%
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
v3: Add a new benchmark result without batching stats patch.
---
mm/memcontrol.c | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index a04b44656b38..ac43c895490a 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -2294,23 +2294,31 @@ static void refill_stock(struct mem_cgroup *memcg, unsigned int nr_pages)
stock_pages = stock->nr_pages[i] + nr_pages;
if (unlikely(stock_pages > MEMCG_STOCK_HIGH)) {
- memcg_uncharge(memcg,
- stock_pages - MEMCG_STOCK_LOW);
- stock_pages = MEMCG_STOCK_LOW;
+ WRITE_ONCE(stock->nr_pages[i], MEMCG_STOCK_LOW);
+ stock_pages -= MEMCG_STOCK_LOW;
+ } else {
+ WRITE_ONCE(stock->nr_pages[i], stock_pages);
+ stock_pages = 0;
}
- WRITE_ONCE(stock->nr_pages[i], stock_pages);
-
local_unlock(&memcg_stock.lock);
+
+ if (unlikely(stock_pages))
+ memcg_uncharge(memcg, stock_pages);
return;
}
+ cached = NULL;
+ stock_pages = 0;
+
i = empty_slot;
if (i == -1) {
i = stock->drain_idx++;
if (stock->drain_idx == NR_MEMCG_STOCK)
stock->drain_idx = 0;
- drain_stock(stock, i);
+
+ cached = stock->cached[i];
+ stock_pages = stock->nr_pages[i];
}
css_get(&memcg->css);
@@ -2318,6 +2326,13 @@ static void refill_stock(struct mem_cgroup *memcg, unsigned int nr_pages)
WRITE_ONCE(stock->nr_pages[i], nr_pages);
local_unlock(&memcg_stock.lock);
+
+ if (unlikely(cached)) {
+ if (stock_pages)
+ memcg_uncharge(cached, stock_pages);
+
+ css_put(&cached->css);
+ }
}
static bool is_memcg_drain_needed(struct memcg_stock_pcp *stock,
--
2.56.0.317.g454f8302e1-goog
next prev parent reply other threads:[~2026-10-29 19:44 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 19:44 [PATCH v3 0/4] memcg: Misc cleanup and perf improvement for TCP Kuniyuki Iwashima
2026-10-29 19:44 ` [PATCH v3 1/4] memcg: Remove unnecessary READ_ONCE() under memcg_stock.lock Kuniyuki Iwashima
2026-10-29 19:58 ` Shakeel Butt
2026-10-29 19:44 ` [PATCH v3 2/4] memcg: Reduce nesting in consume_stock() and refill_stock() Kuniyuki Iwashima
2026-10-29 19:44 ` [PATCH v3 3/4] memcg: Annotate slow paths " Kuniyuki Iwashima
2026-10-29 19:50 ` Shakeel Butt
2026-10-29 19:44 ` Kuniyuki Iwashima [this message]
2026-10-29 19:52 ` [PATCH v3 4/4] memcg: Uncharge pages outside memcg_stock.lock in refill_stock() Shakeel Butt
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261029194452.3189365-5-kuniyu@google.com \
--to=kuniyu@google.com \
--cc=akpm@linux-foundation.org \
--cc=cgroups@vger.kernel.org \
--cc=hannes@cmpxchg.org \
--cc=kuni1840@gmail.com \
--cc=linux-mm@kvack.org \
--cc=mhocko@kernel.org \
--cc=muchun.song@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.