History

mm: memcontrol: fix recursive statistics correctness & scalabilty

Right now, when somebody needs to know the recursive memory statistics and events of a cgroup subtree, they need to walk the entire subtree and sum up the counters manually. There are two issues with this: 1. When a cgroup gets deleted, its stats are lost. The state counters should all be 0 at that point, of course, but the events are not. When this happens, the event counters, which are supposed to be monotonic, can go backwards in the parent cgroups. 2. During regular operation, we always have a certain number of lazily freed cgroups sitting around that have been deleted, have no tasks, but have a few cache pages remaining. These groups' statistics do not change until we eventually hit memory pressure, but somebody watching, say, memory.stat on an ancestor has to iterate those every time. This patch addresses both issues by introducing recursive counters at each level that are propagated from the write side when stats change. Upward propagation happens when the per-cpu caches spill over into the local atomic counter. This is the same thing we do during charge and uncharge, except that the latter uses atomic RMWs, which are more expensive; stat changes happen at around the same rate. In a sparse file test (page faults and reclaim at maximum CPU speed) with 5 cgroup nesting levels, perf shows __mod_memcg_page state at ~1%.

Johannes Weiner committed 7 years ago

Commit 42a30031 parent db9adbc2 files · +150 −109Browse files

Showing the changes to include/linux (1 of 2 files). Show all

Unified · Split

include/linux/memcontrol.h+51−3
@@ -128,6 +128,7 @@ struct mem_cgroup_per_node { · 127 unmodified lines
128128
129129 struct lruvec_stat __percpu *lruvec_stat_cpu;
130130 atomic_long_t lruvec_stat[NR_VM_NODE_STAT_ITEMS];
131+ atomic_long_t lruvec_stat_local[NR_VM_NODE_STAT_ITEMS];
131132
132133 unsigned long lru_zone_size[MAX_NR_ZONES][NR_LRU_LISTS];
133134
@@ -279,8 +280,12 @@ struct mem_cgroup { · 145 unmodified lines
279280 MEMCG_PADDING(_pad2_);
280281
281282 atomic_long_t vmstats[MEMCG_NR_STAT];
283+ atomic_long_t vmstats_local[MEMCG_NR_STAT];
284+
282285 atomic_long_t vmevents[NR_VM_EVENT_ITEMS];
283− atomic_long_t memory_events[MEMCG_NR_MEMORY_EVENTS];
286+ atomic_long_t vmevents_local[NR_VM_EVENT_ITEMS];
287+
288+ atomic_long_t memory_events[MEMCG_NR_MEMORY_EVENTS];
284289
285290 unsigned long socket_pressure;
286291
@@ -550,6 +555,20 @@ struct mem_cgroup *lock_page_memcg(struct page *page); · 263 unmodified lines
550555 void __unlock_page_memcg(struct mem_cgroup *memcg);
551556 void unlock_page_memcg(struct page *page);
552557
558+/*
559+ * idx can be of type enum memcg_stat_item or node_stat_item.
560+ * Keep in sync with memcg_exact_page_state().
561+ */
562+static inline unsigned long memcg_page_state(struct mem_cgroup *memcg, int idx)
563+{
564+ long x = atomic_long_read(&memcg->vmstats[idx]);
565+#ifdef CONFIG_SMP
566+ if (x < 0)
567+ x = 0;
568+#endif
569+ return x;
570+}
571+
553572 /*
554573 * idx can be of type enum memcg_stat_item or node_stat_item.
555574 * Keep in sync with memcg_exact_page_state().
@@ -557,7 +576,7 @@ void unlock_page_memcg(struct page *page); · 1 unmodified line
557576 static inline unsigned long memcg_page_state_local(struct mem_cgroup *memcg,
558577 int idx)
559578 {
560− long x = atomic_long_read(&memcg->vmstats[idx]);
579+ long x = atomic_long_read(&memcg->vmstats_local[idx]);
561580 #ifdef CONFIG_SMP
562581 if (x < 0)
563582 x = 0;
@@ -609,6 +628,24 @@ static inline void mod_memcg_page_state(struct page *page, · 45 unmodified lines
609628 mod_memcg_state(page->mem_cgroup, idx, val);
610629 }
611630
631+static inline unsigned long lruvec_page_state(struct lruvec *lruvec,
632+ enum node_stat_item idx)
633+{
634+ struct mem_cgroup_per_node *pn;
635+ long x;
636+
637+ if (mem_cgroup_disabled())
638+ return node_page_state(lruvec_pgdat(lruvec), idx);
639+
640+ pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
641+ x = atomic_long_read(&pn->lruvec_stat[idx]);
642+#ifdef CONFIG_SMP
643+ if (x < 0)
644+ x = 0;
645+#endif
646+ return x;
647+}
648+
612649 static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec,
613650 enum node_stat_item idx)
614651 {
@@ -619,7 +656,7 @@ static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, · 4 unmodified lines
619656 return node_page_state(lruvec_pgdat(lruvec), idx);
620657
621658 pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
622− x = atomic_long_read(&pn->lruvec_stat[idx]);
659+ x = atomic_long_read(&pn->lruvec_stat_local[idx]);
623660 #ifdef CONFIG_SMP
624661 if (x < 0)
625662 x = 0;
@@ -959,6 +996,11 @@ static inline void mem_cgroup_print_oom_group(struct mem_cgroup *memcg) · 333 unmodified lines
959996 {
960997 }
961998
999+static inline unsigned long memcg_page_state(struct mem_cgroup *memcg, int idx)
1000+{
1001+ return 0;
1002+}
1003+
9621004 static inline unsigned long memcg_page_state_local(struct mem_cgroup *memcg,
9631005 int idx)
9641006 {
@@ -989,6 +1031,12 @@ static inline void mod_memcg_page_state(struct page *page, · 24 unmodified lines
9891031 {
9901032 }
9911033
1034+static inline unsigned long lruvec_page_state(struct lruvec *lruvec,
1035+ enum node_stat_item idx)
1036+{
1037+ return node_page_state(lruvec_pgdat(lruvec), idx);
1038+}
1039+
9921040 static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec,
9931041 enum node_stat_item idx)
9941042 {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t