mm/memcontrol: optimize __memcg_slab_post_alloc_hook batch charging#3
Draft
mm/memcontrol: optimize __memcg_slab_post_alloc_hook batch charging#3
Conversation
…oc_hook
Rewrite the per-object slab memcg charging loop with several improvements:
1. Hoist the loop-invariant max_run computation before the loop.
The cap (INT_MAX - PAGE_SIZE) / obj_size is constant across all
iterations.
2. Replace the single upfront bulk charge + per-object vmstat updates
with per-pgdat-run batching. The scan-ahead loop groups consecutive
objects on the same pgdat node, then:
- obj_cgroup_charge() is called once per run (not once for all
objects upfront)
- mod_objcg_state() is called once per run instead of once per
object, reducing repeated local_lock_irqsave/irqrestore from
N acquisitions to approximately one per pgdat run
- Objects that fail alloc_slab_obj_exts() are simply skipped
rather than being charged upfront and then individually uncharged
3. Avoid redundant virt_to_slab() for the first object of each run
by reusing the slab pointer already computed at the top of the
outer loop.
4. Use run_end absolute index with while (++i < run_end) to advance i
directly, eliminating a separate run_len counter and relative
indexing. The skip_next flag pattern is not needed: when
alloc_slab_obj_exts() fails in the scan-ahead, we simply break
and the next outer iteration retries that object naturally.
Agent-Logs-Url: https://github.com/teawater/linux/sessions/cd4a349b-c91d-46eb-889a-fa3f75c83e73
Co-authored-by: teawater <432382+teawater@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix performance issues in batch memcg charging implementation
mm/memcontrol: optimize __memcg_slab_post_alloc_hook batch charging
Apr 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The per-object memcg charging loop in
__memcg_slab_post_alloc_hook()re-acquiredstock_lockonce per object viamod_objcg_state(), and charged all objects upfront requiring individual uncharg calls whenalloc_slab_obj_exts()failed.Changes
Per-pgdat run batching: scan-ahead groups consecutive objects sharing a pgdat node into a run;
obj_cgroup_charge()andmod_objcg_state()are called once per run instead of once per object — reducinglocal_lock_irqsave/irqrestorefrom N to ~1 for typical same-node bulk allocationsCharge-on-assign: upfront bulk charge (
size * obj_full_size(s)) replaced with per-run charging; objects that failalloc_slab_obj_exts()are skipped entirely, eliminating the per-failureobj_cgroup_uncharge()callHoist loop-invariant
max_run:(INT_MAX - PAGE_SIZE) / obj_sizecomputed once before the loop; also caps run length to keepbatch_byteswithinintrange formod_objcg_state()Reuse first object's
slabpointer:slabfrom the outer loop head is used directly for the first object'sobj_extassignment, saving avirt_to_slab()call per runrun_endabsolute indexing: replacesrun_len+ relativej+skip_nextflag;while (++i < run_end)advancesito the correct position automatically — failedalloc_slab_obj_exts()in scan-ahead simply breaks, and the next outer iteration retries naturallyOriginal prompt
Problem
Commit 7ef269f ("mm/memcontrol: batch memcg charging in __memcg_slab_post_alloc_hook") introduced batch memcg charging but the implementation has several performance issues that cause overhead even when the optimization helps:
Issues in the current code (mm/memcontrol.c, function
__memcg_slab_post_alloc_hook, around line 3280-3364):1.
max_sizerecomputed every outer iteration (line 3307-3308)This value is constant across all iterations —
obj_sizeandsizedon't change. It should be hoisted before the loop.2.
skip_nextflag adds unnecessary complexity (lines 3289, 3318-3319, 3360-3363)When
alloc_slab_obj_exts()fails for an object in the scan-ahead loop, the code setsskip_next = true, breaks, then at the end does:This is unnecessary. Without
skip_next, settingi += run_lenmakes the outer loop land on the failed object. The next iteration callsvirt_to_slab()+alloc_slab_obj_exts()which fails again →i++; continue;— same net result, and this is the rare error path so the one extra check is negligible.3. Redundant
virt_to_slab()callsEach object in a run gets
virt_to_slab()called twice:struct slab *slab_j = virt_to_slab(p[j]);slab = virt_to_slab(p[i + j]);For the first object of each run, it's called three times total (line 3291 + scan-ahead for next run's check + assignment).
The first object's slab pointer (
slabfrom line 3291) is already available but thrown away in the assignment loop which re-computes it atj=0.4. Confusing index arithmetic
The scan-ahead loop uses absolute index
jfromi+1, but the assignment loop uses relativejfrom 0 withp[i + j]. This is error-prone and harder to read.5.
run_leninitialized to 0 then immediately set to 1 (lines 3286, 3300)Minor but sloppy:
size_t run_len = 0;followed byrun_len = 1;a few lines later.Proposed Fix
Optimize the loop in
__memcg_slab_post_alloc_hook()with these changes:Hoist
max_runcomputation before the loop — it's a loop-invariant constant.Remove the
skip_nextflag entirely — simplify the control flow. Whenalloc_slab_obj_exts()fails in scan-ahead, justbreak. The next outer iteration handles it naturally.Avoid redundant
virt_to_slab()for the first object — handle it separately using theslabpointer already computed at the top of the outer loop, then loop over remaining objects.Use
run_endabsolute index — replacerun_len+jrelative indexing withrun_endabsolute index. The assignment loop usesdo { ... } while (++i < run_end)which advancesidirectly, eliminating separate index tracking.Here is the optimized replacement for the loop body in
__memcg_slab_post_alloc_hook()(mm/memcontrol.c). Replace lines 3280-3364 (thefor (i = 0; i < size; )loop and its body) with: