Use local_lock for pcp protection and reduce stat overhead
Subsystems (6)
v6 cover Use local_lock for pcp protection and reduce stat overhead
Changelog since v5
o Rebase 5.13-rc1 on top of Hugh's changes to mm/vmstat.c which means the series no longer has pre-requisites from Andrew's tree
o Not mentioned in changelogs but Sebastian (cc'd) indicated it passed tests replacing the local_lock implementation in the preempt-rt tree
Changelog since v4
o Dropped local_lock embed patch due to complexity
o Fix !NUMA build
o Avoid adding pages with mt >= MIGRATE_PCPTYPES to non-existant per-cpu list
Changelog since v3
o Preserve NUMA_* counters after CPU hotplug
o Drop "mm/page_alloc: Remove duplicate checks if migratetype should be isolated"
o Add micro-optimisation tracking PFN during free_unref_page_list
o Add Acks
Changelog since v2
o Fix zonestats initialisation
o Merged memory hotplug fix separately
o Embed local_lock within per_cpu_pages
This series has no pre-requisites from Andrew's tree any more but is also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.git mm-percpu-local_lock-v6r4
The PCP (per-cpu page allocator in page_alloc.c) shares locking requirements with vmstat and the zone lock which is inconvenient and causes some issues. For example, the PCP list and vmstat share the same per-cpu space meaning that it's possible that vmstat updates dirty cache lines holding per-cpu lists across CPUs unless padding is used. Second, PREEMPT_RT does not want to disable IRQs for too long in the page allocator.
This series splits the locking requirements and uses locks types more suitable for PREEMPT_RT, reduces the time when special locking is required for stats and reduces the time when IRQs need to be disabled on !PREEMPT_RT kernels.
Why local_lock? PREEMPT_RT considers the following sequence to be unsafe as documented in Documentation/locking/locktypes.rst
local_irq_disable(); spin_lock(&lock);
The pcp allocator has this sequence for rmqueue_pcplist (local_irq_save) -> __rmqueue_pcplist -> rmqueue_bulk (spin_lock). While it's possible to separate this out, it generally means there are points where we enable IRQs and reenable them again immediately. To prevent a migration and the per-cpu pointer going stale, migrate_disable is also needed. That is a custom lock that is similar, but worse, than local_lock. Furthermore, on PREEMPT_RT, it's undesirable to leave IRQs disabled for too long. By converting to local_lock which disables migration on PREEMPT_RT, the locking requirements can be separated and start moving the protections for PCP, stats and the zone lock to PREEMPT_RT-safe equivalent locking. As a bonus, local_lock also means that PROVE_LOCKING does something useful.
After that, it's obvious that zone_statistics incurs too much overhead and leaves IRQs disabled for longer than necessary on !PREEMPT_RT kernels. zone_statistics uses perfectly accurate counters requiring IRQs be disabled for parallel RMW sequences when inaccurate ones like vm_events would do. The series makes the NUMA statistics (NUMA_HIT and friends) inaccurate counters that then require no special protection on !PREEMPT_RT.
The bulk page allocator can then do stat updates in bulk with IRQs enabled which should improve the efficiency. Technically, this could have been done without the local_lock and vmstat conversion work and the order simply reflects the timing of when different series were implemented.
Finally, there are places where we conflate IRQs being disabled for the PCP with the IRQ-safe zone spinlock. The remainder of the series reduces the scope of what is protected by disabled IRQs on !PREEMPT_RT kernels. By the end of the series, page_alloc.c does not call local_irq_save so the locking scope is a bit clearer. The one exception is that modifying NR_FREE_PAGES still happens in places where it's known the IRQs are disabled as it's harmless for PREEMPT_RT and would be expensive to split the locking there.
No performance data is included because despite the overhead of the stats, it's within the noise for most workloads on !PREEMPT_RT. However, Jesper Dangaard Brouer ran a page allocation microbenchmark on a E5-1650 v4 @ 3.60GHz CPU on the first version of this series. Focusing on the array variant of the bulk page allocator reveals the following.
(CPU: Intel(R) Xeon(R) CPU E5-1650 v4 @ 3.60GHz) ARRAY variant: time_bulk_page_alloc_free_array: step=bulk size
Baseline Patched 1 56.383 54.225 (+3.83%) 2 40.047 35.492 (+11.38%) 3 37.339 32.643 (+12.58%) 4 35.578 30.992 (+12.89%) 8 33.592 29.606 (+11.87%) 16 32.362 28.532 (+11.85%) 32 31.476 27.728 (+11.91%) 64 30.633 27.252 (+11.04%) 128 30.596 27.090 (+11.46%)
While this is a positive outcome, the series is more likely to be interesting to the RT people in terms of getting parts of the PREEMPT_RT tree into mainline.
drivers/base/node.c | 18 +-- include/linux/mmzone.h | 31 +++-- include/linux/vmstat.h | 65 ++++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 255 ++++++++++++++++++++++++----------------- mm/vmstat.c | 246 +++++++++++++++++---------------------- 6 files changed, 324 insertions(+), 293 deletions(-)
Signature · 1 line
2.26.2
PATCH v6 1/9 mm/page_alloc: Split per cpu page lists and zone stats
The per-cpu page allocator lists and the per-cpu vmstat deltas are stored in the same struct per_cpu_pages even though vmstats have no direct impact on the per-cpu page lists. This is inconsistent because the vmstats for a node are stored on a dedicated structure. The bigger issue is that the per_cpu_pages structure is not cache-aligned and stat updates either cache conflict with adjacent per-cpu lists incurring a runtime cost or padding is required incurring a memory cost.
This patch splits the per-cpu pagelists and the vmstat deltas into separate structures. It's mostly a mechanical conversion but some variable renaming is done to clearly distinguish the per-cpu pages structure (pcp) from the vmstats (pzstats).
Superficially, this appears to increase the size of the per_cpu_pages structure but the movement of expire fills a structure hole so there is no impact overall.
--- include/linux/mmzone.h | 18 ++++---- include/linux/vmstat.h | 8 ++-- mm/page_alloc.c | 85 ++++++++++++++++++++----------------- mm/vmstat.c | 96 ++++++++++++++++++++++-------------------- 4 files changed, 111 insertions(+), 96 deletions(-)
include/linux/mmzone.h +10 -8
@@ -341,20 +341,21 @@ struct per_cpu_pages {int count;/* number of pages in the list */ int high;/* high watermark, emptying needed */ int batch;/* chunk size for buddy add/remove */ +#ifdef CONFIG_NUMA +int expire;/* When 0, remote pagesets are drained */ +#endif /* Lists of pages, one per migrate type stored on the pcp-lists */ struct list_head lists[MIGRATE_PCPTYPES ]; }; -struct per_cpu_pageset { -struct per_cpu_pages pcp; -#ifdef CONFIG_NUMA -s8 expire; -u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ]; -#endif +struct per_cpu_zonestat {#ifdef CONFIG_SMP -s8 stat_threshold;s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS ]; +s8 stat_threshold; +#endif +#ifdef CONFIG_NUMA +u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ];#endif };
@@ -484,7 +485,8 @@ struct zone {int node;#endif struct pglist_data *zone_pgdat; -struct per_cpu_pageset __percpu *pageset; +struct per_cpu_pages __percpu *per_cpu_pageset; +struct per_cpu_zonestat __percpu *per_cpu_zonestats;/* * the high and batch values are copied to individual pagesets for * faster access
include/linux/vmstat.h +4 -4
@@ -163,7 +163,7 @@ static inline unsigned long zone_numa_state_snapshot(struct zone *zone,int cpu;for_each_online_cpu (cpu) - x +=per_cpu_ptr (zone->pageset, cpu)->vm_numa_stat_diff[item]; + x +=per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_numa_stat_diff[item];return x; }
@@ -236,7 +236,7 @@ static inline unsigned long zone_page_state_snapshot(struct zone *zone,#ifdef CONFIG_SMP int cpu;for_each_online_cpu (cpu) - x +=per_cpu_ptr (zone->pageset, cpu)->vm_stat_diff[item]; + x +=per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_stat_diff[item];if (x <0 ) x =0 ;
@@ -291,7 +291,7 @@ struct ctl_table;int vmstat_refresh (struct ctl_table *,int write,void *buffer,size_t *lenp,loff_t *ppos); -void drain_zonestat (struct zone *zone,struct per_cpu_pageset *); +void drain_zonestat (struct zone *zone,struct per_cpu_zonestat *);int calculate_pressure_threshold (struct zone *zone);int calculate_normal_threshold (struct zone *zone);
@@ -399,7 +399,7 @@ static inline void cpu_vm_stats_fold(int cpu) { }static inline void quiet_vmstat (void ) { }static inline void drain_zonestat (struct zone *zone, -struct per_cpu_pageset *pset) { } +struct per_cpu_zonestat *pzstats) { }#endif /* CONFIG_SMP */ static inline void __mod_zone_freepage_state(struct zone *zone, int nr_pages,
mm/page_alloc.c +47 -38
212 lines not on this page · raw message
mm/vmstat.c +50 -46
250 lines not on this page · raw message
Signature · 1 line
2.26.2
PATCH v6 2/9 mm/page_alloc: Convert per-cpu list protection to local_lock
In reply to Mel GormanThere is a lack of clarity of what exactly local_irq_save/local_irq_restore protects in page_alloc.c . It conflates the protection of per-cpu page allocation structures with per-cpu vmstat deltas.
This patch protects the PCP structure using local_lock which for most configurations is identical to IRQ enabling/disabling. The scope of the lock is still wider than it should be but this is decreased later.
It is possible for the local_lock to be embedded safely within struct per_cpu_pages but it adds complexity to free_unref_page_list.
--- include/linux/mmzone.h | 2 ++ mm/page_alloc.c | 50 +++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 15 deletions(-)
include/linux/mmzone.h +2 -0
@@ -20,6 +20,7 @@#include <linux/atomic.h> #include <linux/mm_types.h> #include <linux/page-flags.h> +#include <linux/local_lock.h> #include <asm/page.h> /* Free memory management - zoned buddy allocator. */
@@ -337,6 +338,7 @@ enum zone_watermarks {#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH] + z->watermark_boost)#define wmark_pages (z, i) (z->_watermark[i] + z->watermark_boost) +/* Fields and list protected by pagesets local_lock in page_alloc.c */ struct per_cpu_pages {int count;/* number of pages in the list */ int high;/* high watermark, emptying needed */
mm/page_alloc.c +35 -15
145 lines not on this page · raw message
Signature · 1 line
2.26.2
PATCH v6 3/9 mm/vmstat: Convert NUMA statistics to basic NUMA counters
In reply to Mel GormanNUMA statistics are maintained on the zone level for hits, misses, foreign etc but nothing relies on them being perfectly accurate for functional correctness. The counters are used by userspace to get a general overview of a workloads NUMA behaviour but the page allocator incurs a high cost to maintain perfect accuracy similar to what is required for a vmstat like NR_FREE_PAGES. There even is a sysctl vm.numa_stat to allow userspace to turn off the collection of NUMA statistics like NUMA_HIT.
This patch converts NUMA_HIT and friends to be NUMA events with similar accuracy to VM events. There is a possibility that slight errors will be introduced but the overall trend as seen by userspace will be similar. The counters are no longer updated from vmstat_refresh context as it is unnecessary overhead for counters that may never be read by userspace. Note that counters could be maintained at the node level to save space but it would have a user-visible impact due to /proc/zoneinfo.
--- drivers/base/node.c | 18 +++-- include/linux/mmzone.h | 13 ++- include/linux/vmstat.h | 43 +++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 12 +-- mm/vmstat.c | 175 ++++++++++++++++------------------------- 6 files changed, 115 insertions(+), 148 deletions(-)
drivers/base/node.c +10 -8
@@ -482,6 +482,7 @@ static DEVICE_ATTR(meminfo, 0444, node_read_meminfo, NULL);static ssize_t node_read_numastat (struct device *dev,struct device_attribute *attr,char *buf) { +fold_vm_numa_events ();return sysfs_emit (buf,"numa_hit %lu\n" "numa_miss %lu\n"
@@ -489,12 +490,12 @@ static ssize_t node_read_numastat(struct device *dev,"interleave_hit %lu\n" "local_node %lu\n" "other_node %lu\n" , -sum_zone_numa_state (dev->id,NUMA_HIT ), -sum_zone_numa_state (dev->id,NUMA_MISS ), -sum_zone_numa_state (dev->id,NUMA_FOREIGN ), -sum_zone_numa_state (dev->id,NUMA_INTERLEAVE_HIT ), -sum_zone_numa_state (dev->id,NUMA_LOCAL ), - sum_zone_numa_state(dev->id, NUMA_OTHER)); +sum_zone_numa_event_state (dev->id,NUMA_HIT ), +sum_zone_numa_event_state (dev->id,NUMA_MISS ), +sum_zone_numa_event_state (dev->id,NUMA_FOREIGN ), +sum_zone_numa_event_state (dev->id,NUMA_INTERLEAVE_HIT ), +sum_zone_numa_event_state (dev->id,NUMA_LOCAL ), + sum_zone_numa_event_state(dev->id, NUMA_OTHER)); } static DEVICE_ATTR(numastat, 0444, node_read_numastat, NULL);
@@ -512,10 +513,11 @@ static ssize_t node_read_vmstat(struct device *dev, sum_zone_node_page_state(nid, i));#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) +fold_vm_numa_events (); +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++) len +=sysfs_emit_at (buf, len,"%s %lu\n" ,numa_stat_name (i), -sum_zone_numa_state (nid, i)); +sum_zone_numa_event_state (nid, i));#endif for (i =0 ; i <NR_VM_NODE_STAT_ITEMS ; i++) {
include/linux/mmzone.h +9 -4
@@ -135,10 +135,10 @@ enum numa_stat_item {NUMA_INTERLEAVE_HIT ,/* interleaver preferred this zone */ NUMA_LOCAL ,/* allocation from local node */ NUMA_OTHER ,/* allocation from other node */ -NR_VM_NUMA_STAT_ITEMS +NR_VM_NUMA_EVENT_ITEMS };#else -#define NR_VM_NUMA_STAT_ITEMS 0 +#define NR_VM_NUMA_EVENT_ITEMS 0#endif enum zone_stat_item {
@@ -357,7 +357,12 @@ struct per_cpu_zonestat {s8 stat_threshold;#endif #ifdef CONFIG_NUMA -u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ]; +/* +* Low priority inaccurate counters that are only folded +* on demand. Use a large type to avoid the overhead of +* folding during refresh_cpu_vm_stats. +*/ +unsigned long vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ];#endif };
@@ -623,7 +628,7 @@ struct zone {ZONE_PADDING (_pad3_)/* Zone statistics */ atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; -atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS ]; +atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ]; }____cacheline_internodealigned_in_smp ;enum pgdat_flags {
include/linux/vmstat.h +20 -23
93 lines not on this page · raw message
mm/mempolicy.c +1 -1
@@ -2150,7 +2150,7 @@ static struct page *alloc_page_interleave(gfp_t gfp, unsigned order,return page;if (page &&page_to_nid (page) == nid) {preempt_disable (); -__inc_numa_state (page_zone (page),NUMA_INTERLEAVE_HIT ); +__count_numa_event (page_zone (page),NUMA_INTERLEAVE_HIT );preempt_enable (); }return page;
mm/page_alloc.c +6 -6
@@ -3469,12 +3469,12 @@ static inline void zone_statistics(struct zone *preferred_zone, struct zone *z) local_stat =NUMA_OTHER ;if (zone_to_nid (z) ==zone_to_nid (preferred_zone)) -__inc_numa_state (z,NUMA_HIT ); +__count_numa_event (z,NUMA_HIT );else { -__inc_numa_state (z,NUMA_MISS ); -__inc_numa_state (preferred_zone,NUMA_FOREIGN ); +__count_numa_event (z,NUMA_MISS ); +__count_numa_event (preferred_zone,NUMA_FOREIGN ); } -__inc_numa_state (z, local_stat); +__count_numa_event (z, local_stat);#endif }
@@ -6746,8 +6746,8 @@ void __init setup_per_cpu_pageset(void)*/ for_each_possible_cpu (cpu) {struct per_cpu_zonestat *pzstats = &per_cpu (boot_zonestats, cpu); -memset (pzstats->vm_numa_stat_diff,0 , -sizeof (pzstats->vm_numa_stat_diff)); +memset (pzstats->vm_numa_event,0 , +sizeof (pzstats->vm_numa_event)); }#endif
mm/vmstat.c +69 -106
344 lines not on this page · raw message
Signature · 1 line
2.26.2
Re: [PATCH 3/9] mm/vmstat: Convert NUMA statistics to basic NUMA counters
Quoted 16 lines · Mel Gorman
NUMA statistics are maintained on the zone level for hits, misses, foreign etc but nothing relies on them being perfectly accurate for functional correctness. The counters are used by userspace to get a general overview of a workloads NUMA behaviour but the page allocator incurs a high cost to maintain perfect accuracy similar to what is required for a vmstat like
NR_FREE_PAGES. There even is a sysctl vm.numa_stat to allow userspace to turn off the collection of NUMA statistics likeNUMA_HIT.This patch converts
NUMA_HITand friends to be NUMA events with similar accuracy to VM events. There is a possibility that slight errors will be introduced but the overall trend as seen by userspace will be similar. The counters are no longer updated fromvmstat_refreshcontext as it is unnecessary overhead for counters that may never be read by userspace. Note that counters could be maintained at the node level to save space but it would have a user-visible impact due to/proc/zoneinfo.
Quoted 2 lines
[[email protected]: Fix misplaced closing brace for !CONFIG_NUMA]Signed-off-by Mel Gorman <[email protected]>
Quoted 611 lines
--- drivers/base/node.c | 18 +++-- include/linux/mmzone.h | 13 ++- include/linux/vmstat.h | 43 +++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 12 +-- mm/vmstat.c | 175 ++++++++++++++++------------------------- 6 files changed, 115 insertions(+), 148 deletions(-)drivers/base/node.c +10
-8@@ -482,6 +482,7 @@ static DEVICE_ATTR(meminfo, 0444, node_read_meminfo, NULL);static ssize_t node_read_numastat (struct device *dev,struct device_attribute *attr,char *buf) { +fold_vm_numa_events ();return sysfs_emit (buf,"numa_hit %lu\n" "numa_miss %lu\n"@@ -489,12 +490,12 @@ static ssize_t node_read_numastat(struct device *dev,"interleave_hit %lu\n" "local_node %lu\n" "other_node %lu\n" , -sum_zone_numa_state (dev->id,NUMA_HIT ), -sum_zone_numa_state (dev->id,NUMA_MISS ), -sum_zone_numa_state (dev->id,NUMA_FOREIGN ), -sum_zone_numa_state (dev->id,NUMA_INTERLEAVE_HIT ), -sum_zone_numa_state (dev->id,NUMA_LOCAL ), - sum_zone_numa_state(dev->id, NUMA_OTHER)); +sum_zone_numa_event_state (dev->id,NUMA_HIT ), +sum_zone_numa_event_state (dev->id,NUMA_MISS ), +sum_zone_numa_event_state (dev->id,NUMA_FOREIGN ), +sum_zone_numa_event_state (dev->id,NUMA_INTERLEAVE_HIT ), +sum_zone_numa_event_state (dev->id,NUMA_LOCAL ), + sum_zone_numa_event_state(dev->id, NUMA_OTHER)); } static DEVICE_ATTR(numastat, 0444, node_read_numastat, NULL);@@ -512,10 +513,11 @@ static ssize_t node_read_vmstat(struct device *dev, sum_zone_node_page_state(nid, i));#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) +fold_vm_numa_events (); +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++) len +=sysfs_emit_at (buf, len,"%s %lu\n" ,numa_stat_name (i), -sum_zone_numa_state (nid, i)); +sum_zone_numa_event_state (nid, i));#endif for (i =0 ; i <NR_VM_NODE_STAT_ITEMS ; i++) {include/linux/mmzone.h +9
-4@@ -135,10 +135,10 @@ enum numa_stat_item {NUMA_INTERLEAVE_HIT ,/* interleaver preferred this zone */ NUMA_LOCAL ,/* allocation from local node */ NUMA_OTHER ,/* allocation from other node */ -NR_VM_NUMA_STAT_ITEMS +NR_VM_NUMA_EVENT_ITEMS };#else -#define NR_VM_NUMA_STAT_ITEMS 0 +#define NR_VM_NUMA_EVENT_ITEMS 0#endif enum zone_stat_item {@@ -357,7 +357,12 @@ struct per_cpu_zonestat {s8 stat_threshold;#endif #ifdef CONFIG_NUMA -u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ]; +/* +* Low priority inaccurate counters that are only folded +* on demand. Use a large type to avoid the overhead of +* folding during refresh_cpu_vm_stats. +*/ +unsigned long vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ];#endif };@@ -623,7 +628,7 @@ struct zone {ZONE_PADDING (_pad3_)/* Zone statistics */ atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; -atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS ]; +atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ]; }____cacheline_internodealigned_in_smp ;enum pgdat_flags {include/linux/vmstat.h +20
-23@@ -138,34 +138,27 @@ static inline void vm_events_fold_cpu(int cpu)* Zone and node-based page accounting with per cpu differentials. */ extern atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS ]; -extern atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS ];extern atomic_long_t vm_node_stat[NR_VM_NODE_STAT_ITEMS ]; +extern atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ];#ifdef CONFIG_NUMA -static inline void zone_numa_state_add (long x,struct zone *zone, -enum numa_stat_item item) +static inline void zone_numa_event_add (long x,struct zone *zone, +enum numa_stat_item item) { -atomic_long_add (x, &zone->vm_numa_stat[item]); -atomic_long_add (x, &vm_numa_stat[item]); +atomic_long_add (x, &zone->vm_numa_event[item]); +atomic_long_add (x, &vm_numa_event[item]); } -static inline unsigned long global_numa_state (enum numa_stat_item item) +static inline unsigned long zone_numa_event_state (struct zone *zone, +enum numa_stat_item item) { -long x =atomic_long_read (&vm_numa_stat[item]); - -return x; +return atomic_long_read (&zone->vm_numa_event[item]); } -static inline unsigned long zone_numa_state_snapshot (struct zone *zone, -enum numa_stat_item item) +static inline unsigned long +global_numa_event_state (enum numa_stat_item item) { -long x =atomic_long_read (&zone->vm_numa_stat[item]); -int cpu; - -for_each_online_cpu (cpu) - x +=per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_numa_stat_diff[item]; - -return x; +return atomic_long_read (&vm_numa_event[item]); }#endif /* CONFIG_NUMA */ @@ -245,18 +238,22 @@ static inline unsigned long zone_page_state_snapshot(struct zone *zone, }#ifdef CONFIG_NUMA -extern void __inc_numa_state (struct zone *zone,enum numa_stat_item item); +extern void __count_numa_event (struct zone *zone,enum numa_stat_item item);extern unsigned long sum_zone_node_page_state (int node,enum zone_stat_item item); -extern unsigned long sum_zone_numa_state (int node,enum numa_stat_item item); +extern unsigned long sum_zone_numa_event_state (int node,enum numa_stat_item item);extern unsigned long node_page_state (struct pglist_data *pgdat,enum node_stat_item item);extern unsigned long node_page_state_pages (struct pglist_data *pgdat,enum node_stat_item item); +extern void fold_vm_numa_events (void );#else #define sum_zone_node_page_state (node, item) global_zone_page_state(item)#define node_page_state (node, item) global_node_page_state(item)#define node_page_state_pages (node, item) global_node_page_state_pages(item) +static inline void fold_vm_numa_events (void ) +{ +}#endif /* CONFIG_NUMA */ #ifdef CONFIG_SMP @@ -428,7 +425,7 @@ static inline const char *numa_stat_name(enum numa_stat_item item)static inline const char *node_stat_name (enum node_stat_item item) {return vmstat_text[NR_VM_ZONE_STAT_ITEMS + -NR_VM_NUMA_STAT_ITEMS + +NR_VM_NUMA_EVENT_ITEMS + item]; }@@ -440,7 +437,7 @@ static inline const char *lru_list_name(enum lru_list lru)static inline const char *writeback_stat_name (enum writeback_stat_item item) {return vmstat_text[NR_VM_ZONE_STAT_ITEMS + -NR_VM_NUMA_STAT_ITEMS + +NR_VM_NUMA_EVENT_ITEMS +NR_VM_NODE_STAT_ITEMS + item]; }@@ -449,7 +446,7 @@ static inline const char *writeback_stat_name(enum writeback_stat_item item)static inline const char *vm_event_name (enum vm_event_item item) {return vmstat_text[NR_VM_ZONE_STAT_ITEMS + -NR_VM_NUMA_STAT_ITEMS + +NR_VM_NUMA_EVENT_ITEMS +NR_VM_NODE_STAT_ITEMS +NR_VM_WRITEBACK_STAT_ITEMS + item];mm/mempolicy.c +1
-1@@ -2150,7 +2150,7 @@ static struct page *alloc_page_interleave(gfp_t gfp, unsigned order,return page;if (page &&page_to_nid (page) == nid) {preempt_disable (); -__inc_numa_state (page_zone (page),NUMA_INTERLEAVE_HIT ); +__count_numa_event (page_zone (page),NUMA_INTERLEAVE_HIT );preempt_enable (); }return page;mm/page_alloc.c +6
-6@@ -3469,12 +3469,12 @@ static inline void zone_statistics(struct zone *preferred_zone, struct zone *z) local_stat =NUMA_OTHER ;if (zone_to_nid (z) ==zone_to_nid (preferred_zone)) -__inc_numa_state (z,NUMA_HIT ); +__count_numa_event (z,NUMA_HIT );else { -__inc_numa_state (z,NUMA_MISS ); -__inc_numa_state (preferred_zone,NUMA_FOREIGN ); +__count_numa_event (z,NUMA_MISS ); +__count_numa_event (preferred_zone,NUMA_FOREIGN ); } -__inc_numa_state (z, local_stat); +__count_numa_event (z, local_stat);#endif }@@ -6746,8 +6746,8 @@ void __init setup_per_cpu_pageset(void)*/ for_each_possible_cpu (cpu) {struct per_cpu_zonestat *pzstats = &per_cpu (boot_zonestats, cpu); -memset (pzstats->vm_numa_stat_diff,0 , -sizeof (pzstats->vm_numa_stat_diff)); +memset (pzstats->vm_numa_event,0 , +sizeof (pzstats->vm_numa_event)); }#endif mm/vmstat.c +69
-106@@ -31,8 +31,6 @@#include "internal.h" -#define NUMA_STATS_THRESHOLD (U16_MAX - 2) -#ifdef CONFIG_NUMA int sysctl_vm_numa_stat =ENABLE_NUMA_STAT ;@@ -41,11 +39,12 @@ static void zero_zone_numa_counters(struct zone *zone) {int item, cpu; -for (item =0 ; item <NR_VM_NUMA_STAT_ITEMS ; item++) { -atomic_long_set (&zone->vm_numa_stat[item],0 ); -for_each_online_cpu (cpu) -per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_numa_stat_diff[item] +for (item =0 ; item <NR_VM_NUMA_EVENT_ITEMS ; item++) { +atomic_long_set (&zone->vm_numa_event[item],0 ); +for_each_online_cpu (cpu) { +per_cpu_ptr (zone->per_cpu_zonestats, cpu)->vm_numa_event[item] =0 ; + } } }@@ -63,8 +62,8 @@ static void zero_global_numa_counters(void) {int item; -for (item =0 ; item <NR_VM_NUMA_STAT_ITEMS ; item++) -atomic_long_set (&vm_numa_stat[item],0 ); +for (item =0 ; item <NR_VM_NUMA_EVENT_ITEMS ; item++) +atomic_long_set (&vm_numa_event[item],0 ); }static void invalid_numa_statistics (void )@@ -161,10 +160,9 @@ void vm_events_fold_cpu(int cpu)* vm_stat contains the global counters */ atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS] __cacheline_aligned_in_smp; -atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS] __cacheline_aligned_in_smp; atomic_long_t vm_node_stat[NR_VM_NODE_STAT_ITEMS] __cacheline_aligned_in_smp; +atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS] __cacheline_aligned_in_smp;EXPORT_SYMBOL (vm_zone_stat); -EXPORT_SYMBOL (vm_numa_stat);EXPORT_SYMBOL (vm_node_stat);#ifdef CONFIG_SMP @@ -706,8 +704,7 @@ EXPORT_SYMBOL(dec_node_page_state);* Fold a differential into the global counters. * Returns the number of counters updated. */ -#ifdef CONFIG_NUMA -static int fold_diff (int *zone_diff,int *numa_diff,int *node_diff) +static int fold_diff (int *zone_diff,int *node_diff) {int i;int changes =0 ;@@ -718,12 +715,6 @@ static int fold_diff(int *zone_diff, int *numa_diff, int *node_diff) changes++; } -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) -if (numa_diff[i]) { -atomic_long_add (numa_diff[i], &vm_numa_stat[i]); - changes++; - } -for (i =0 ; i <NR_VM_NODE_STAT_ITEMS ; i++)if (node_diff[i]) {atomic_long_add (node_diff[i], &vm_node_stat[i]);@@ -731,26 +722,34 @@ static int fold_diff(int *zone_diff, int *numa_diff, int *node_diff) }return changes; } -#else -static int fold_diff (int *zone_diff,int *node_diff) + +#ifdef CONFIG_NUMA +static void fold_vm_zone_numa_events (struct zone *zone) { -int i; -int changes =0 ; +unsigned long zone_numa_events[NR_VM_NUMA_EVENT_ITEMS ] = {0 , }; +int cpu; +enum numa_stat_item item; -for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++) -if (zone_diff[i]) { -atomic_long_add (zone_diff[i], &vm_zone_stat[i]); - changes++; - } +for_each_online_cpu (cpu) { +struct per_cpu_zonestat *pzstats; -for (i =0 ; i <NR_VM_NODE_STAT_ITEMS ; i++) -if (node_diff[i]) { -atomic_long_add (node_diff[i], &vm_node_stat[i]); - changes++; + pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); +for (item =0 ; item <NR_VM_NUMA_EVENT_ITEMS ; item++) + zone_numa_events[item] +=xchg (&pzstats->vm_numa_event[item],0 ); } -return changes; + +for (item =0 ; item <NR_VM_NUMA_EVENT_ITEMS ; item++) +zone_numa_event_add (zone_numa_events[item], zone, item); } -#endif /* CONFIG_NUMA */ + +void fold_vm_numa_events (void ) +{ +struct zone *zone; + +for_each_populated_zone (zone) +fold_vm_zone_numa_events (zone); +} +#endif /* * Update the zone counters for the current cpu. @@ -774,15 +773,14 @@ static int refresh_cpu_vm_stats(bool do_pagesets)struct zone *zone;int i;int global_zone_diff[NR_VM_ZONE_STAT_ITEMS ] = {0 , }; -#ifdef CONFIG_NUMA -int global_numa_diff[NR_VM_NUMA_STAT_ITEMS ] = {0 , }; -#endif int global_node_diff[NR_VM_NODE_STAT_ITEMS ] = {0 , };int changes =0 ;for_each_populated_zone (zone) {struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; +#ifdef CONFIG_NUMA struct per_cpu_pages __percpu *pcp = zone->per_cpu_pageset; +#endif for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++) {int v;@@ -799,17 +797,6 @@ static int refresh_cpu_vm_stats(bool do_pagesets) } }#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) { -int v; - - v =this_cpu_xchg (pzstats->vm_numa_stat_diff[i],0 ); -if (v) { - -atomic_long_add (v, &zone->vm_numa_stat[i]); - global_numa_diff[i] += v; -__this_cpu_write (pcp->expire,3 ); - } - }if (do_pagesets) {cond_resched ();@@ -857,12 +844,7 @@ static int refresh_cpu_vm_stats(bool do_pagesets) } } -#ifdef CONFIG_NUMA - changes +=fold_diff (global_zone_diff, global_numa_diff, - global_node_diff); -#else changes +=fold_diff (global_zone_diff, global_node_diff); -#endif return changes; }@@ -877,9 +859,6 @@ void cpu_vm_stats_fold(int cpu)struct zone *zone;int i;int global_zone_diff[NR_VM_ZONE_STAT_ITEMS ] = {0 , }; -#ifdef CONFIG_NUMA -int global_numa_diff[NR_VM_NUMA_STAT_ITEMS ] = {0 , }; -#endif int global_node_diff[NR_VM_NODE_STAT_ITEMS ] = {0 , };for_each_populated_zone (zone) {@@ -887,7 +866,7 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); -for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++) +for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++) {if (pzstats->vm_stat_diff[i]) {int v;@@ -896,17 +875,17 @@ void cpu_vm_stats_fold(int cpu)atomic_long_add (v, &zone->vm_stat[i]); global_zone_diff[i] += v; } - + }#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) -if (pzstats->vm_numa_stat_diff[i]) { -int v; +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++) { +if (pzstats->vm_numa_event[i]) { +unsigned long v; - v = pzstats->vm_numa_stat_diff[i]; - pzstats->vm_numa_stat_diff[i] =0 ; -atomic_long_add (v, &zone->vm_numa_stat[i]); - global_numa_diff[i] += v; + v = pzstats->vm_numa_event[i]; + pzstats->vm_numa_event[i] =0 ; +zone_numa_event_add (v, zone, i); } + }#endif }@@ -926,11 +905,7 @@ void cpu_vm_stats_fold(int cpu) } } -#ifdef CONFIG_NUMA -fold_diff (global_zone_diff, global_numa_diff, global_node_diff); -#else fold_diff (global_zone_diff, global_node_diff); -#endif }/* @@ -939,43 +914,37 @@ void cpu_vm_stats_fold(int cpu)*/ void drain_zonestat (struct zone *zone,struct per_cpu_zonestat *pzstats) { +unsigned long v;int i; -for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++) +for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++) {if (pzstats->vm_stat_diff[i]) { -int v = pzstats->vm_stat_diff[i]; + v = pzstats->vm_stat_diff[i]; pzstats->vm_stat_diff[i] =0 ; -atomic_long_add (v, &zone->vm_stat[i]); -atomic_long_add (v, &vm_zone_stat[i]); +zone_page_state_add (v, zone, i); } + }#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) -if (pzstats->vm_numa_stat_diff[i]) { -int v = pzstats->vm_numa_stat_diff[i]; - - pzstats->vm_numa_stat_diff[i] =0 ; -atomic_long_add (v, &zone->vm_numa_stat[i]); -atomic_long_add (v, &vm_numa_stat[i]); +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++) { +if (pzstats->vm_numa_event[i]) { + v = pzstats->vm_numa_event[i]; + pzstats->vm_numa_event[i] =0 ; +zone_numa_event_add (v, zone, i); } + }#endif }#endif #ifdef CONFIG_NUMA -void __inc_numa_state (struct zone *zone, +/* See __count_vm_event comment on why raw_cpu_inc is used. */ +void __count_numa_event (struct zone *zone,enum numa_stat_item item) {struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; -u16 __percpu *p = pzstats->vm_numa_stat_diff + item; -u16 v; - - v =__this_cpu_inc_return (*p); -if (unlikely (v >NUMA_STATS_THRESHOLD )) { -zone_numa_state_add (v, zone, item); -__this_cpu_write (*p,0 ); - } +raw_cpu_inc (pzstats->vm_numa_event[item]); }/* @@ -996,19 +965,16 @@ unsigned long sum_zone_node_page_state(int node,return count; } -/* -* Determine the per node value of a numa stat item. To avoid deviation, -* the per cpu stat number in vm_numa_stat_diff[] is also included. -*/ -unsigned long sum_zone_numa_state (int node, +/* Determine the per node value of a numa stat item. */ +unsigned long sum_zone_numa_event_state (int node,enum numa_stat_item item) {struct zone *zones =NODE_DATA (node)->node_zones; -int i;unsigned long count =0 ; +int i;for (i =0 ; i <MAX_NR_ZONES ; i++) - count +=zone_numa_state_snapshot (zones + i, item); + count +=zone_numa_event_state (zones + i, item);return count; }@@ -1687,9 +1653,9 @@ static void zoneinfo_show_print(struct seq_file *m, pg_data_t *pgdat, zone_page_state(zone, i));#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++)seq_printf (m,"\n %-12s %lu" ,numa_stat_name (i), -zone_numa_state_snapshot (zone, i)); +zone_numa_event_state (zone, i));#endif seq_printf (m,"\n pagesets" );@@ -1743,7 +1709,7 @@ static const struct seq_operations zoneinfo_op = { };#define NR_VMSTAT_ITEMS (NR_VM_ZONE_STAT_ITEMS + \ - NR_VM_NUMA_STAT_ITEMS + \ + NR_VM_NUMA_EVENT_ITEMS + \ NR_VM_NODE_STAT_ITEMS + \ NR_VM_WRITEBACK_STAT_ITEMS + \ (IS_ENABLED(CONFIG_VM_EVENT_COUNTERS) ? \@@ -1758,6 +1724,7 @@ static void *vmstat_start(struct seq_file *m, loff_t *pos)return NULL ;BUILD_BUG_ON (ARRAY_SIZE (vmstat_text) <NR_VMSTAT_ITEMS ); +fold_vm_numa_events (); v =kmalloc_array (NR_VMSTAT_ITEMS ,sizeof (unsigned long ),GFP_KERNEL ); m->private = v;if (!v)@@ -1767,9 +1734,9 @@ static void *vmstat_start(struct seq_file *m, loff_t *pos) v +=NR_VM_ZONE_STAT_ITEMS ;#ifdef CONFIG_NUMA -for (i =0 ; i <NR_VM_NUMA_STAT_ITEMS ; i++) - v[i] =global_numa_state (i); - v +=NR_VM_NUMA_STAT_ITEMS ; +for (i =0 ; i <NR_VM_NUMA_EVENT_ITEMS ; i++) + v[i] =global_numa_event_state (i); + v +=NR_VM_NUMA_EVENT_ITEMS ;#endif for (i =0 ; i <NR_VM_NODE_STAT_ITEMS ; i++) {@@ -1939,11 +1906,7 @@ static bool need_update(int cpu)if (memchr_inv (pzstats->vm_stat_diff,0 ,NR_VM_ZONE_STAT_ITEMS *sizeof (pzstats->vm_stat_diff[0 ])))return true; -#ifdef CONFIG_NUMA -if (memchr_inv (pzstats->vm_numa_stat_diff,0 ,NR_VM_NUMA_STAT_ITEMS * -sizeof (pzstats->vm_numa_stat_diff[0 ]))) -return true; -#endif +if (last_pgdat == zone->zone_pgdat)continue ; last_pgdat = zone->zone_pgdat;
PATCH v6 4/9 mm/vmstat: Inline NUMA event counter updates
In reply to Mel Gorman__count_numa_event is small enough to be treated similarly to __count_vm_event so inline it.
--- include/linux/vmstat.h | 10 +++++++++- mm/vmstat.c | 9 --------- 2 files changed, 9 insertions(+), 10 deletions(-)
include/linux/vmstat.h +9 -1
@@ -238,7 +238,15 @@ static inline unsigned long zone_page_state_snapshot(struct zone *zone, }#ifdef CONFIG_NUMA -extern void __count_numa_event (struct zone *zone,enum numa_stat_item item); +/* See __count_vm_event comment on why raw_cpu_inc is used. */ +static inline void +__count_numa_event (struct zone *zone,enum numa_stat_item item) +{ +struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; + +raw_cpu_inc (pzstats->vm_numa_event[item]); +} +extern unsigned long sum_zone_node_page_state (int node,enum zone_stat_item item);extern unsigned long sum_zone_numa_event_state (int node,enum numa_stat_item item);
mm/vmstat.c +0 -9
@@ -938,15 +938,6 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats)#endif #ifdef CONFIG_NUMA -/* See __count_vm_event comment on why raw_cpu_inc is used. */ -void __count_numa_event (struct zone *zone, -enum numa_stat_item item) -{ -struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; - -raw_cpu_inc (pzstats->vm_numa_event[item]); -} -/* * Determine the per node value of a stat item. This function * is called frequently in a NUMA machine, so try to be as
Signature · 1 line
2.26.2
PATCH v6 5/9 mm/page_alloc: Batch the accounting updates in the bulk allocator
In reply to Mel GormanNow that the zone_statistics are simple counters that do not require special protection, the bulk allocator accounting updates can be batch updated without adding too much complexity with protected RMW updates or using xchg.
--- include/linux/vmstat.h | 8 ++++++++ mm/page_alloc.c | 30 +++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-)
include/linux/vmstat.h +8 -0
@@ -247,6 +247,14 @@ __count_numa_event(struct zone *zone, enum numa_stat_item item)raw_cpu_inc (pzstats->vm_numa_event[item]); } +static inline void +__count_numa_events (struct zone *zone,enum numa_stat_item item,long delta) +{ +struct per_cpu_zonestat __percpu *pzstats = zone->per_cpu_zonestats; + +raw_cpu_add (pzstats->vm_numa_event[item], delta); +} +extern unsigned long sum_zone_node_page_state (int node,enum zone_stat_item item);extern unsigned long sum_zone_numa_event_state (int node,enum numa_stat_item item);
mm/page_alloc.c +13 -17
@@ -3456,7 +3456,8 @@ void __putback_isolated_page(struct page *page, unsigned int order, int mt)* * Must be called with interrupts disabled. */ -static inline void zone_statistics (struct zone *preferred_zone,struct zone *z) +static inline void zone_statistics (struct zone *preferred_zone,struct zone *z, +long nr_account) {#ifdef CONFIG_NUMA enum numa_stat_item local_stat =NUMA_LOCAL ;
@@ -3469,12 +3470,12 @@ static inline void zone_statistics(struct zone *preferred_zone, struct zone *z) local_stat =NUMA_OTHER ;if (zone_to_nid (z) ==zone_to_nid (preferred_zone)) -__count_numa_event (z,NUMA_HIT ); +__count_numa_events (z,NUMA_HIT , nr_account);else { -__count_numa_event (z,NUMA_MISS ); -__count_numa_event (preferred_zone,NUMA_FOREIGN ); +__count_numa_events (z,NUMA_MISS , nr_account); +__count_numa_events (preferred_zone,NUMA_FOREIGN , nr_account); } -__count_numa_event (z, local_stat); +__count_numa_events (z, local_stat, nr_account);#endif }
@@ -3520,7 +3521,7 @@ static struct page *rmqueue_pcplist(struct zone *preferred_zone, page =__rmqueue_pcplist (zone, migratetype, alloc_flags, pcp, list);if (page) {__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 ); -zone_statistics (preferred_zone, zone); +zone_statistics (preferred_zone, zone,1 ); }local_unlock_irqrestore (&pagesets.lock, flags);return page;
@@ -3581,7 +3582,7 @@ struct page *rmqueue(struct zone *preferred_zone, get_pcppage_migratetype(page));__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 << order); -zone_statistics (preferred_zone, zone); +zone_statistics (preferred_zone, zone,1 );local_irq_restore (flags); out:
@@ -5063,7 +5064,7 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid,struct alloc_context ac;gfp_t alloc_gfp;unsigned int alloc_flags =ALLOC_WMARK_LOW ; -int nr_populated =0 ; +int nr_populated =0 , nr_account =0 ;if (unlikely (nr_pages <=0 ))return 0 ;
@@ -5136,15 +5137,7 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, goto failed_irq;break ; } - -/* -* Ideally this would be batched but the best way to do -* that cheaply is to first convert zone_statistics to -* be inaccurate per-cpu counter like vm_events to avoid -* a RMW cycle then do the accounting with IRQs enabled. -*/ -__count_zid_vm_events (PGALLOC ,zone_idx (zone),1 ); -zone_statistics (ac.preferred_zoneref->zone, zone); + nr_account++;prep_new_page (page,0 , gfp,0 );if (page_list)
@@ -5154,6 +5147,9 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, nr_populated++; } +__count_zid_vm_events (PGALLOC ,zone_idx (zone), nr_account); +zone_statistics (ac.preferred_zoneref->zone, zone, nr_account); +local_unlock_irqrestore (&pagesets.lock, flags);return nr_populated;
Signature · 1 line
2.26.2
PATCH v6 6/9 mm/page_alloc: Reduce duration that IRQs are disabled for VM counters
In reply to Mel GormanIRQs are left disabled for the zone and node VM event counters. This is unnecessary as the affected counters are allowed to race for preemmption and IRQs.
This patch reduces the scope of IRQs being disabled
via local_[lock|unlock]_irq on !PREEMPT_RT kernels. One __mod_zone_freepage_state is still called with IRQs disabled. While this could be moved out, it's not free on all architectures as some require IRQs to be disabled for mod_zone_page_state on !PREEMPT_RT kernels.
--- mm/page_alloc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
mm/page_alloc.c +6 -6
@@ -3519,11 +3519,11 @@ static struct page *rmqueue_pcplist(struct zone *preferred_zone, pcp =this_cpu_ptr (zone->per_cpu_pageset); list = &pcp->lists[migratetype]; page =__rmqueue_pcplist (zone, migratetype, alloc_flags, pcp, list); +local_unlock_irqrestore (&pagesets.lock, flags);if (page) {__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 );zone_statistics (preferred_zone, zone,1 ); } -local_unlock_irqrestore (&pagesets.lock, flags);return page; }
@@ -3575,15 +3575,15 @@ struct page *rmqueue(struct zone *preferred_zone,if (!page) page =__rmqueue (zone, order, migratetype, alloc_flags); }while (page &&check_new_pages (page, order)); -spin_unlock (&zone->lock);if (!page) goto failed; +__mod_zone_freepage_state (zone, -(1 << order),get_pcppage_migratetype (page)); +spin_unlock_irqrestore (&zone->lock, flags);__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 << order);zone_statistics (preferred_zone, zone,1 ); -local_irq_restore (flags); out:/* Separate test+clear to avoid unnecessary atomics */
@@ -3596,7 +3596,7 @@ struct page *rmqueue(struct zone *preferred_zone,return page; failed: -local_irq_restore (flags); +spin_unlock_irqrestore (&zone->lock, flags);return NULL ; }
@@ -5147,11 +5147,11 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, nr_populated++; } +local_unlock_irqrestore (&pagesets.lock, flags); +__count_zid_vm_events (PGALLOC ,zone_idx (zone), nr_account);zone_statistics (ac.preferred_zoneref->zone, zone, nr_account); -local_unlock_irqrestore (&pagesets.lock, flags); -return nr_populated; failed_irq:
Signature · 1 line
2.26.2
PATCH v6 7/9 mm/page_alloc: Explicitly acquire the zone lock in __free_pages_ok
In reply to Mel Gorman__free_pages_ok() disables IRQs before calling a common helper free_one_page() that acquires the zone lock. This is not safe according to Documentation/locking/locktypes.rst and in this context, IRQ disabling is not protecting a per_cpu_pages structure either or a local_lock would be used.
This patch explicitly acquires the lock with spin_lock_irqsave instead of relying on a helper. This removes the last instance of local_irq_save() in page_alloc.c.
--- mm/page_alloc.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-)
mm/page_alloc.c +8 -8
@@ -1579,21 +1579,21 @@ static void __free_pages_ok(struct page *page, unsigned int order,unsigned long flags;int migratetype;unsigned long pfn =page_to_pfn (page); +struct zone *zone =page_zone (page);if (!free_pages_prepare (page, order, true, fpi_flags))return ; migratetype =get_pfnblock_migratetype (page, pfn); -/* -* TODO FIX: Disable IRQs before acquiring IRQ-safe zone->lock -* and protect vmstat updates. -*/ -local_irq_save (flags); +spin_lock_irqsave (&zone->lock, flags);__count_vm_events (PGFREE ,1 << order); -free_one_page (page_zone (page), page, pfn, order, migratetype, - fpi_flags); -local_irq_restore (flags); +if (unlikely (has_isolate_pageblock (zone) || +is_migrate_isolate (migratetype))) { + migratetype =get_pfnblock_migratetype (page, pfn); + } +__free_one_page (page, pfn, zone, order, migratetype, fpi_flags); +spin_unlock_irqrestore (&zone->lock, flags); }void __free_pages_core (struct page *page,unsigned int order)
Signature · 1 line
2.26.2
PATCH v6 8/9 mm/page_alloc: Avoid conflating IRQs disabled with zone->lock
In reply to Mel GormanHistorically when freeing pages, free_one_page() assumed that callers had IRQs disabled and the zone->lock could be acquired with spin_lock(). This confuses the scope of what local_lock_irq is protecting and what zone->lock is protecting in free_unref_page_list in particular.
This patch uses spin_lock_irqsave() for the zone->lock in free_one_page() instead of relying on callers to have disabled IRQs. free_unref_page_commit() is changed to only deal with PCP pages protected by the local lock. free_unref_page_list() then first frees isolated pages to the buddy lists with free_one_page() and frees the rest of the pages to the PCP via free_unref_page_commit(). The end result is that free_one_page() is no longer depending on side-effects of local_lock to be correct.
Note that this may incur a performance penalty while memory hot-remove is running but that is not a common operation.
--- mm/page_alloc.c | 75 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 26 deletions(-)
mm/page_alloc.c +49 -26
@@ -1490,13 +1490,15 @@ static void free_one_page(struct zone *zone,unsigned int order, int migratetype, fpi_t fpi_flags) { - spin_lock(&zone->lock); + unsigned long flags; + +spin_lock_irqsave (&zone->lock, flags);if (unlikely (has_isolate_pageblock (zone) ||is_migrate_isolate (migratetype))) { migratetype =get_pfnblock_migratetype (page, pfn); }__free_one_page (page, pfn, zone, order, migratetype, fpi_flags); -spin_unlock (&zone->lock); +spin_unlock_irqrestore (&zone->lock, flags); } static void __meminit __init_single_page(struct page *page, unsigned long pfn,
@@ -3274,31 +3276,13 @@ static bool free_unref_page_prepare(struct page *page, unsigned long pfn)return true; } -static void free_unref_page_commit (struct page *page,unsigned long pfn) +static void free_unref_page_commit (struct page *page,unsigned long pfn, +int migratetype) {struct zone *zone =page_zone (page);struct per_cpu_pages *pcp; -int migratetype; - migratetype =get_pcppage_migratetype (page);__count_vm_event (PGFREE ); - -/* -* We only track unmovable, reclaimable and movable on pcp lists. -* Free ISOLATE pages back to the allocator because they are being -* offlined but treat HIGHATOMIC as movable pages so we can get those -* areas back if necessary. Otherwise, we may have to free -* excessively into the page allocator -*/ -if (migratetype >=MIGRATE_PCPTYPES ) { -if (unlikely (is_migrate_isolate (migratetype))) { -free_one_page (zone, page, pfn,0 , migratetype, -FPI_NONE ); -return ; - } - migratetype =MIGRATE_MOVABLE ; - } - pcp =this_cpu_ptr (zone->per_cpu_pageset);list_add (&page->lru, &pcp->lists[migratetype]); pcp->count++;
@@ -3313,12 +3297,29 @@ void free_unref_page(struct page *page) {unsigned long flags;unsigned long pfn =page_to_pfn (page); +int migratetype;if (!free_unref_page_prepare (page, pfn))return ; +/* +* We only track unmovable, reclaimable and movable on pcp lists. +* Place ISOLATE pages on the isolated list because they are being +* offlined but treat HIGHATOMIC as movable pages so we can get those +* areas back if necessary. Otherwise, we may have to free +* excessively into the page allocator +*/ + migratetype =get_pcppage_migratetype (page); +if (unlikely (migratetype >=MIGRATE_PCPTYPES )) { +if (unlikely (is_migrate_isolate (migratetype))) { +free_one_page (page_zone (page), page, pfn,0 , migratetype,FPI_NONE ); +return ; + } + migratetype =MIGRATE_MOVABLE ; + } +local_lock_irqsave (&pagesets.lock, flags); -free_unref_page_commit (page, pfn); +free_unref_page_commit (page, pfn, migratetype);local_unlock_irqrestore (&pagesets.lock, flags); }
@@ -3330,22 +3331,44 @@ void free_unref_page_list(struct list_head *list)struct page *page, *next;unsigned long flags, pfn;int batch_count =0 ; +int migratetype;/* Prepare pages for freeing */ list_for_each_entry_safe (page, next, list, lru) { pfn =page_to_pfn (page);if (!free_unref_page_prepare (page, pfn))list_del (&page->lru); + +/* +* Free isolated pages directly to the allocator, see +* comment in free_unref_page. +*/ + migratetype =get_pcppage_migratetype (page); +if (unlikely (migratetype >=MIGRATE_PCPTYPES )) { +if (unlikely (is_migrate_isolate (migratetype))) { +list_del (&page->lru); +free_one_page (page_zone (page), page, pfn,0 , + migratetype,FPI_NONE ); +continue ; + } + +/* +* Non-isolated types over MIGRATE_PCPTYPES get added +* to the MIGRATE_MOVABLE pcp list. +*/ +set_pcppage_migratetype (page,MIGRATE_MOVABLE ); + } +set_page_private (page, pfn); }local_lock_irqsave (&pagesets.lock, flags);list_for_each_entry_safe (page, next, list, lru) { -unsigned long pfn =page_private (page); - + pfn =page_private (page);set_page_private (page,0 ); + migratetype =get_pcppage_migratetype (page);trace_mm_page_free_batched (page); -free_unref_page_commit (page, pfn); +free_unref_page_commit (page, pfn, migratetype);/* * Guard against excessive IRQ disabled times when we get
Signature · 1 line
2.26.2
PATCH v6 9/9 mm/page_alloc: Update PGFREE outside the zone lock in __free_pages_ok
In reply to Mel GormanVM events do not need explicit protection by disabling IRQs so update the counter with IRQs enabled in __free_pages_ok.
--- mm/page_alloc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
mm/page_alloc.c +2 -1
@@ -1589,13 +1589,14 @@ static void __free_pages_ok(struct page *page, unsigned int order, migratetype =get_pfnblock_migratetype (page, pfn);spin_lock_irqsave (&zone->lock, flags); -__count_vm_events (PGFREE ,1 << order);if (unlikely (has_isolate_pageblock (zone) ||is_migrate_isolate (migratetype))) { migratetype =get_pfnblock_migratetype (page, pfn); }__free_one_page (page, pfn, zone, order, migratetype, fpi_flags);spin_unlock_irqrestore (&zone->lock, flags); + +__count_vm_events (PGFREE ,1 << order); }void __free_pages_core (struct page *page,unsigned int order)
Signature · 1 line
2.26.2
Quoted 8 lines · Mel Gorman
drivers/base/node.c | 18 +-- include/linux/mmzone.h | 31 +++-- include/linux/vmstat.h | 65 ++++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 255 ++++++++++++++++++++++++----------------- mm/vmstat.c | 246 +++++++++++++++++---------------------- 6 files changed, 324 insertions(+), 293 deletions(-)
LGTM,