History

mm: memcg: factor out memcg- and lruvec-level changes out of __mod_lruvec_state()

Patch series "The new cgroup slab memory controller", v7. The patchset moves the accounting from the page level to the object level. It allows to share slab pages between memory cgroups. This leads to a significant win in the slab utilization (up to 45%) and the corresponding drop in the total kernel memory footprint. The reduced number of unmovable slab pages should also have a positive effect on the memory fragmentation. The patchset makes the slab accounting code simpler: there is no more need in the complicated dynamic creation and destruction of per-cgroup slab caches, all memory cgroups use a global set of shared slab caches. The lifetime of slab caches is not more connected to the lifetime of memory cgroups. The more precise accounting does require more CPU, however in practice the difference seems to be negligible. We've been using the new slab controller in Facebook production for several months with different workloads and haven't seen any noticeable regressions. What we've seen were memory savings in order of 1 GB per host (it varied heavily depending on the actual workload, size of RAM, number of CPUs, memory pressure, etc). The third version of the patchset added yet another step towards the simplification of the code: sharing of slab caches between accounted and non-accounted allocations. It comes with significant upsides (most noticeable, a complete elimination of dynamic slab caches creation) but not without some regression risks, so this change sits on top of the patchset and is not completely merged in. So in the unlikely event of a noticeable performance regression it can be reverted separately. The slab memory accounting works in exactly the same way for SLAB and SLUB. With both allocators the new controller shows significant memory savings, with SLUB the difference is bigger. On my 16-core desktop machine running Fedora 32 the size of the slab memory measured after the start of the system was lower by 58% and 38% with SLUB and SLAB correspondingly. As an estimation of a potential CPU overhead, below are results of slab_bulk_test01 test, kindly provided by Jesper D. Brouer. He also helped with the evaluation of results. The test can be found here: https://github.com/netoptimizer/prototype-kernel/ The smallest number in each row should be picked for a comparison. SLUB-patched - bulk-API - SLUB-patched : bulk_quick_reuse objects=1 : 187 - 90 - 224 cycles(tsc) - SLUB-patched : bulk_quick_reuse objects=2 : 110 - 53 - 133 cycles(tsc) - SLUB-patched : bulk_quick_reuse objects=3 : 88 - 95 - 42 cycles(tsc) - SLUB-patched : bulk_quick_reuse objects=4 : 91 - 85 - 36 cycles(tsc) - SLUB-patched : bulk_quick_reuse objects=8 : 32 - 66 - 32 cycles(tsc) SLUB-original - bulk-API - SLUB-original: bulk_quick_reuse objects=1 : 87 - 87 - 142 cycles(tsc) - SLUB-original: bulk_quick_reuse objects=2 : 52 - 53 - 53 cycles(tsc) - SLUB-original: bulk_quick_reuse objects=3 : 42 - 42 - 91 cycles(tsc) - SLUB-original: bulk_quick_reuse objects=4 : 91 - 37 - 37 cycles(tsc) - SLUB-original: bulk_quick_reuse objects=8 : 31 - 79 - 76 cycles(tsc) SLAB-patched - bulk-API - SLAB-patched : bulk_quick_reuse objects=1 : 67 - 67 - 140 cycles(tsc) - SLAB-patched : bulk_quick_reuse objects=2 : 55 - 46 - 46 cycles(tsc) - SLAB-patched : bulk_quick_reuse objects=3 : 93 - 94 - 39 cycles(tsc) - SLAB-patched : bulk_quick_reuse objects=4 : 35 - 88 - 85 cycles(tsc) - SLAB-patched : bulk_quick_reuse objects=8 : 30 - 30 - 30 cycles(tsc) SLAB-original- bulk-API - SLAB-original: bulk_quick_reuse objects=1 : 143 - 136 - 67 cycles(tsc) - SLAB-original: bulk_quick_reuse objects=2 : 45 - 46 - 46 cycles(tsc) - SLAB-original: bulk_quick_reuse objects=3 : 38 - 39 - 39 cycles(tsc) - SLAB-original: bulk_quick_reuse objects=4 : 35 - 87 - 87 cycles(tsc) - SLAB-original: bulk_quick_reuse objects=8 : 29 - 66 - 30 cycles(tsc) This patch (of 19): To convert memcg and lruvec slab counters to bytes there must be a way to change these counters without touching node counters. Factor out __mod_memcg_lruvec_state() out of __mod_lruvec_state().

Roman Gushchin committed 6 years ago

Commit eedc4e51 parent d648bcc2 files · +41 −19Browse files

Unified · Split

2 files
  1. include/linux/memcontrol.h +17 −0
  2. mm/memcontrol.c +24 −19
include/linux/memcontrol.h+17−0
@@ -679,11 +679,23 @@ static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, · 678 unmodified lines
679 return x;679 return x;
680}680}
681 681
682void __mod_memcg_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx,
683 int val);
682void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx,684void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx,
683 int val);685 int val);
684void __mod_lruvec_slab_state(void *p, enum node_stat_item idx, int val);686void __mod_lruvec_slab_state(void *p, enum node_stat_item idx, int val);
685void mod_memcg_obj_state(void *p, int idx, int val);687void mod_memcg_obj_state(void *p, int idx, int val);
686 688
689static inline void mod_memcg_lruvec_state(struct lruvec *lruvec,
690 enum node_stat_item idx, int val)
691{
692 unsigned long flags;
693
694 local_irq_save(flags);
695 __mod_memcg_lruvec_state(lruvec, idx, val);
696 local_irq_restore(flags);
697}
698
687static inline void mod_lruvec_state(struct lruvec *lruvec,699static inline void mod_lruvec_state(struct lruvec *lruvec,
688 enum node_stat_item idx, int val)700 enum node_stat_item idx, int val)
689{701{
@@ -1057,6 +1069,11 @@ static inline unsigned long lruvec_page_state_local(struct lruvec *lruvec, · 367 unmodified lines
1057 return node_page_state(lruvec_pgdat(lruvec), idx);1069 return node_page_state(lruvec_pgdat(lruvec), idx);
1058}1070}
1059 1071
1072static inline void __mod_memcg_lruvec_state(struct lruvec *lruvec,
1073 enum node_stat_item idx, int val)
1074{
1075}
1076
1060static inline void __mod_lruvec_state(struct lruvec *lruvec,1077static inline void __mod_lruvec_state(struct lruvec *lruvec,
1061 enum node_stat_item idx, int val)1078 enum node_stat_item idx, int val)
1062{1079{
mm/memcontrol.c+24−19
@@ -713,30 +713,13 @@ parent_nodeinfo(struct mem_cgroup_per_node *pn, int nid) · 712 unmodified lines
713 return mem_cgroup_nodeinfo(parent, nid);713 return mem_cgroup_nodeinfo(parent, nid);
714}714}
715 715
716/**716void __mod_memcg_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx,
717 * __mod_lruvec_state - update lruvec memory statistics717 int val)
718 * @lruvec: the lruvec
719 * @idx: the stat item
720 * @val: delta to add to the counter, can be negative
721 *
722 * The lruvec is the intersection of the NUMA node and a cgroup. This
723 * function updates the all three counters that are affected by a
724 * change of state at this level: per-node, per-cgroup, per-lruvec.
725 */
726void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx,
727 int val)
728{718{
729 pg_data_t *pgdat = lruvec_pgdat(lruvec);
730 struct mem_cgroup_per_node *pn;719 struct mem_cgroup_per_node *pn;
731 struct mem_cgroup *memcg;720 struct mem_cgroup *memcg;
732 long x;721 long x;
733 722
734 /* Update node */
735 __mod_node_page_state(pgdat, idx, val);
736
737 if (mem_cgroup_disabled())
738 return;
739
740 pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);723 pn = container_of(lruvec, struct mem_cgroup_per_node, lruvec);
741 memcg = pn->memcg;724 memcg = pn->memcg;
742 725
@@ -748,6 +731,7 @@ void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx, · 5 unmodified lines
748 731
749 x = val + __this_cpu_read(pn->lruvec_stat_cpu->count[idx]);732 x = val + __this_cpu_read(pn->lruvec_stat_cpu->count[idx]);
750 if (unlikely(abs(x) > MEMCG_CHARGE_BATCH)) {733 if (unlikely(abs(x) > MEMCG_CHARGE_BATCH)) {
734 pg_data_t *pgdat = lruvec_pgdat(lruvec);
751 struct mem_cgroup_per_node *pi;735 struct mem_cgroup_per_node *pi;
752 736
753 for (pi = pn; pi; pi = parent_nodeinfo(pi, pgdat->node_id))737 for (pi = pn; pi; pi = parent_nodeinfo(pi, pgdat->node_id))
@@ -757,6 +741,27 @@ void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx, · 3 unmodified lines
757 __this_cpu_write(pn->lruvec_stat_cpu->count[idx], x);741 __this_cpu_write(pn->lruvec_stat_cpu->count[idx], x);
758}742}
759 743
744/**
745 * __mod_lruvec_state - update lruvec memory statistics
746 * @lruvec: the lruvec
747 * @idx: the stat item
748 * @val: delta to add to the counter, can be negative
749 *
750 * The lruvec is the intersection of the NUMA node and a cgroup. This
751 * function updates the all three counters that are affected by a
752 * change of state at this level: per-node, per-cgroup, per-lruvec.
753 */
754void __mod_lruvec_state(struct lruvec *lruvec, enum node_stat_item idx,
755 int val)
756{
757 /* Update node */
758 __mod_node_page_state(lruvec_pgdat(lruvec), idx, val);
759
760 /* Update memcg and lruvec */
761 if (!mem_cgroup_disabled())
762 __mod_memcg_lruvec_state(lruvec, idx, val);
763}
764
760void __mod_lruvec_slab_state(void *p, enum node_stat_item idx, int val)765void __mod_lruvec_slab_state(void *p, enum node_stat_item idx, int val)
761{766{
762 pg_data_t *pgdat = page_pgdat(virt_to_page(p));767 pg_data_t *pgdat = page_pgdat(virt_to_page(p));