Use local_lock for pcp protection and reduce stat overhead
Subsystems (6)
cover Use local_lock for pcp protection and reduce stat overhead
This series requires patches in Andrew's tree so the series is also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.git mm-percpu-local_lock-v1r15
tldr: Jesper and Chuck, it would be nice to verify if this series helps
the allocation rate of the bulk page allocator. RT people, this *partially* addresses some problems PREEMPT_RT has with the page allocator but it needs review.
The PCP (per-cpu page allocator in page_alloc.c) share locking requirements with vmstat which is inconvenient and causes some issues. Possibly because of that, 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. The series splits that structure and separates the locking.
Second, 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). This series explicitly separates the locking requirements for the PCP list (local_lock) and stat updates (irqs disabled). Once that is done, the length of time IRQs are disabled can be reduced and in some cases, IRQ disabling can be replaced with preempt_disable.
After that, it was very obvious that zone_statistics in particular has way too much overhead and leaves IRQs disabled for longer than necessary. It has 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 only require preempt be disabled.
Finally the bulk page allocator can then do all the stat updates in bulk with IRQs enabled which should improve the efficiency of the bulk page allocator. 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.
No performance data is included because despite the overhead of the stats, it's within the noise for most workloads but Jesper and Chuck may observe a significant different with the same tests used for the bulk page allocator. The series is more likely to be interesting to the RT folk in terms of slowing getting the PREEMPT tree into mainline.
drivers/base/node.c | 18 +-- include/linux/mmzone.h | 29 +++-- include/linux/vmstat.h | 65 ++++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 173 ++++++++++++++++------------ mm/vmstat.c | 254 +++++++++++++++-------------------------- 6 files changed, 254 insertions(+), 287 deletions(-)
Signature · 1 line
2.26.2
PATCH 1/6 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 | 84 +++++++++++++++++++----------------- mm/vmstat.c | 96 ++++++++++++++++++++++-------------------- 4 files changed, 110 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 };
@@ -470,7 +471,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 +46 -38
213 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 2/6 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 in later patches. The per-cpu vmstat deltas are protected by preempt_disable/preempt_enable where necessary instead of relying on IRQ disable/enable.
--- include/linux/mmzone.h | 2 ++ mm/page_alloc.c | 43 ++++++++++++++++++++++++------------------ mm/vmstat.c | 4 ++++ 3 files changed, 31 insertions(+), 18 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 +25 -18
131 lines not on this page · raw message
mm/vmstat.c +4 -0
@@ -887,6 +887,7 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); +preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v;
@@ -908,6 +909,7 @@ void cpu_vm_stats_fold(int cpu) global_numa_diff[i] += v; }#endif +preempt_enable (); }for_each_online_pgdat (pgdat) {
@@ -941,6 +943,7 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats) {int i; +preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v = pzstats->vm_stat_diff[i];
@@ -959,6 +962,7 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats)atomic_long_add (v, &vm_numa_stat[i]); }#endif +preempt_enable (); }#endif
Signature · 1 line
2.26.2
Re: [PATCH 2/6] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 10 lines · Mel Gorman
There 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_lockwhich 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 in later patches. The per-cpu vmstat deltas are protected by preempt_disable/preempt_enable where necessary instead of relying on IRQ disable/enable.
Yes, this goes into the right direction and I really appreciate the scoped protection for clarity sake.
mm/vmstat.c void cpu_vm_stats_fold(int cpu) · line 890
Quoted 10 lines
#ifdef
CONFIG_MEMORY_HOTREMOVEmm/vmstat.c +1
-0@@ -887,6 +887,7 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); +preempt_disable ();
What's the reason for the preempt_disable() here? A comment would be appreciated.
Quoted 12 lines
mm/vmstat.c +1
-0for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v;@@ -908,6 +909,7 @@ void cpu_vm_stats_fold(int cpu) global_numa_diff[i] += v; }#endif +preempt_enable (); }for_each_online_pgdat (pgdat) {
mm/vmstat.c void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats) · line 946
Quoted 4 lines
mm/vmstat.c +1
-0@@ -941,6 +943,7 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats) {int i; +preempt_disable ();
Same here.
mm/vmstat.c void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats) · line 965
Quoted 8 lines
mm/vmstat.c +1
-0for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v = pzstats->vm_stat_diff[i];@@ -959,6 +962,7 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats) atomic_long_add(v, &vm_numa_stat[i]); }#endif +preempt_enable ();
Thanks,
tglx
Re: [PATCH 2/6] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 12 lines · Thomas Gleixner
Quoted 10 lines · Mel Gorman
There 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_lockwhich 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 in later patches. The per-cpu vmstat deltas are protected by preempt_disable/preempt_enable where necessary instead of relying on IRQ disable/enable.
Quoted 3 lines
Yes, this goes into the right direction and I really appreciate the scoped protection for clarity sake.
Thanks.
Quoted 7 lines
Quoted 7 lines
#ifdef
CONFIG_MEMORY_HOTREMOVEmm/vmstat.c +0
-0@@ -887,6 +887,7 @@ void cpu_vm_stats_fold(int cpu)
Quoted 7 lines
mm/vmstat.c void cpu_vm_stats_fold(int cpu) · line 890
Quoted 3 lines
mm/vmstat.c +1
-0pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); +preempt_disable ();What's the reason for the
preempt_disable()here? A comment would be appreciated.
Very good question because it's protecting vm_stat_diff and vm_numa_stat_diff in different contexts and not quite correctly at this point of the series. By the end of the series vm_numa_stat_diff is a simple counter and does not need special protection.
Right now, it's protecting against a read and clear of vm_stat_diff in two contexts -- cpu_vm_stats_fold and drain_zonestats but it's only defensive. cpu_vm_stats_fold is only called when a CPU is going dead and drain_zonestats is called from memory hotplug context. The protection is necessary only if a new drain_zonestats caller was added without taking the RMW of vm_stat_diff into account which may never happen.
This whole problem with preemption could be avoided altogether if this_cpu_xchg was used similar to what is done elsewhere in vmstat so.... this?
mm/page_alloc.c +3 -2
@@ -8969,8 +8969,9 @@ void zone_pcp_reset(struct zone *zone)struct per_cpu_zonestat *pzstats;/* -* No race with drain_pages. drain_zonestat disables preemption -* and drain_pages relies on the pcp local_lock. +* No race with drain_pages. drain_zonestat is only concerned with +* vm_*_stat_diff which is updated with this_cpu_xchg and drain_pages +* only cares about the PCP lists protected by local_lock. */ if (zone->per_cpu_pageset != &boot_pageset) {for_each_online_cpu (cpu) {
mm/vmstat.c +4 -11
@@ -887,13 +887,11 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); -preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v; - v = pzstats->vm_stat_diff[i]; - pzstats->vm_stat_diff[i] =0 ; + v =this_cpu_xchg (pzstats->vm_stat_diff[i],0 );atomic_long_add (v, &zone->vm_stat[i]); global_zone_diff[i] += v; }
@@ -903,13 +901,11 @@ void cpu_vm_stats_fold(int cpu)if (pzstats->vm_numa_stat_diff[i]) {int v; - v = pzstats->vm_numa_stat_diff[i]; - pzstats->vm_numa_stat_diff[i] =0 ; + v =this_cpu_xchg (pzstats->vm_numa_stat_diff[i],0 );atomic_long_add (v, &zone->vm_numa_stat[i]); global_numa_diff[i] += v; }#endif -preempt_enable (); }for_each_online_pgdat (pgdat) {
@@ -943,10 +939,9 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats) {int i; -preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) { -int v = pzstats->vm_stat_diff[i]; +int v =this_cpu_xchg (pzstats->vm_stat_diff[i],0 ); pzstats->vm_stat_diff[i] =0 ;atomic_long_add (v, &zone->vm_stat[i]);atomic_long_add (v, &vm_zone_stat[i]);
@@ -955,14 +950,12 @@ void drain_zonestat(struct zone *zone, struct per_cpu_zonestat *pzstats)#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]; +int v =this_cpu_xchg (pzstats->vm_numa_stat_diff[i],0 ); - pzstats->vm_numa_stat_diff[i] =0 ;atomic_long_add (v, &zone->vm_numa_stat[i]);atomic_long_add (v, &vm_numa_stat[i]); } #endif - preempt_enable(); }#endif
Signature · 2 lines
Mel Gorman SUSE Labs
Re: [PATCH 2/6] mm/page_alloc: Convert per-cpu list protection to local_lock
mm/vmstat.c void cpu_vm_stats_fold(int cpu) · line 894
Quoted 13 lines · Mel Gorman
On Wed, Mar 31, 2021 at 11:55:56AM +0200, Thomas Gleixner wrote:
mm/vmstat.c +1
-3@@ -887,13 +887,11 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); -preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v; - v = pzstats->vm_stat_diff[i]; - pzstats->vm_stat_diff[i] =0 ; + v =this_cpu_xchg (pzstats->vm_stat_diff[i],0 );
Confused. pzstats is not a percpu pointer. zone->per_cpu_zonestats is.
But @cpu is not necessarily the current CPU.
Thanks,
tglx
Re: [PATCH 2/6] mm/page_alloc: Convert per-cpu list protection to local_lock
Quoted 11 lines · Thomas Gleixner
Quoted 10 lines · Mel Gorman
On Wed, Mar 31, 2021 at 11:55:56AM +0200, Thomas Gleixner wrote:
mm/vmstat.c +0
-1@@ -887,13 +887,11 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); -preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v;
Quoted 7 lines
mm/vmstat.c void cpu_vm_stats_fold(int cpu) · line 894
Quoted 3 lines
mm/vmstat.c +1
-2- v = pzstats->vm_stat_diff[i]; - pzstats->vm_stat_diff[i] =0 ; + v =this_cpu_xchg (pzstats->vm_stat_diff[i],0 );Confused. pzstats is not a percpu pointer.
zone->per_cpu_zonestatsis.But @cpu is not necessarily the current CPU.
@cpu _is_ definitely NOT the current CPU as this is invoked from the hotplug callback _after_ @cpu went dead. @cpu is dead and wont update these things concurrently. So I'm even more confused :)
Thanks,
tglx
Re: [PATCH 2/6] mm/page_alloc: Convert per-cpu list protection to local_lock
In reply to Thomas GleixnerQuoted 11 lines · Thomas Gleixner
Quoted 10 lines · Mel Gorman
On Wed, Mar 31, 2021 at 11:55:56AM +0200, Thomas Gleixner wrote:
mm/vmstat.c +0
-1@@ -887,13 +887,11 @@ void cpu_vm_stats_fold(int cpu) pzstats =per_cpu_ptr (zone->per_cpu_zonestats, cpu); -preempt_disable ();for (i =0 ; i <NR_VM_ZONE_STAT_ITEMS ; i++)if (pzstats->vm_stat_diff[i]) {int v;
Quoted 8 lines
mm/vmstat.c void cpu_vm_stats_fold(int cpu) · line 894
Quoted 3 lines
mm/vmstat.c +1
-2- v = pzstats->vm_stat_diff[i]; - pzstats->vm_stat_diff[i] =0 ; + v =this_cpu_xchg (pzstats->vm_stat_diff[i],0 );Confused. pzstats is not a percpu pointer.
zone->per_cpu_zonestatsis.But @cpu is not necessarily the current CPU.
I was drinking drain cleaner instead of coffee. The code was also broken to begin with.
drain_pages() is draining pagesets of a local or dead CPU. For a local CPU, disabling IRQs prevent an IRQ arriving during the drain, trying to allocate a page and potentially corrupt the local pageset -- ok.
zone_pcp_reset is accessing a remote CPUs pageset, freeing the percpu pointer and resetting it to boot_pageset. zone_pcp_reset calling local_irq_save() does not offer any special protection against drain_pages because there are two separate IRQs involved.
This particular patch may have no reason to touch zone_pcp_reset, cpu_vm_stats_fold or drain_zonestat at all but I need to think about it more tomorrow.
Signature · 2 lines
Mel Gorman SUSE Labs
PATCH 3/6 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. This is overkill as the counters are used by userspace to get a general overview 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. Note that while these counters could be maintained at the node level, it would have a user-visible impact.
--- drivers/base/node.c | 18 +++-- include/linux/mmzone.h | 11 ++- include/linux/vmstat.h | 42 +++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 12 +-- mm/vmstat.c | 175 ++++++++++++----------------------------- 6 files changed, 93 insertions(+), 167 deletions(-)
drivers/base/node.c +10 -8
@@ -484,6 +484,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"
@@ -491,12 +492,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);
@@ -514,10 +515,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 +7 -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,10 @@ struct per_cpu_zonestat {s8 stat_threshold;#endif #ifdef CONFIG_NUMA -u16 vm_numa_stat_diff[NR_VM_NUMA_STAT_ITEMS ]; +u16 vm_numa_stat_diff[NR_VM_NUMA_EVENT_ITEMS ]; +#endif +#ifdef CONFIG_NUMA +unsigned long vm_numa_event[NR_VM_NUMA_EVENT_ITEMS ];#endif };
@@ -609,7 +612,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_events[NR_VM_NUMA_EVENT_ITEMS ]; }____cacheline_internodealigned_in_smp ;enum pgdat_flags {
include/linux/vmstat.h +19 -23
93 lines not on this page · raw message
mm/mempolicy.c +1 -1
@@ -2146,7 +2146,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
@@ -3411,12 +3411,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 }
@@ -6687,8 +6687,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 +50 -125
339 lines not on this page · raw message
Signature · 1 line
2.26.2
PATCH 4/6 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 | 9 +++++++++ mm/vmstat.c | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-)
include/linux/vmstat.h +9 -0
@@ -237,6 +237,15 @@ static inline unsigned long zone_page_state_snapshot(struct zone *zone, }#ifdef CONFIG_NUMA +/* 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 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);
mm/vmstat.c +0 -9
@@ -906,15 +906,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 5/6 mm/page_alloc: Batch the accounting updates in the bulk allocator
In reply to Mel GormanNow that the zone_statistics are a simple counter that does not require special protection, the bulk allocator accounting updates can be batch updated without requiring IRQs to be disabled.
--- include/linux/vmstat.h | 8 ++++++++ mm/page_alloc.c | 30 +++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-)
include/linux/vmstat.h +8 -0
@@ -246,6 +246,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 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);
mm/page_alloc.c +13 -17
@@ -3398,7 +3398,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 ;
@@ -3411,12 +3412,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 }
@@ -3462,7 +3463,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;
@@ -3523,7 +3524,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:
@@ -5006,7 +5007,7 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid,struct alloc_context ac;gfp_t alloc_gfp;unsigned int alloc_flags; -int nr_populated =0 ; +int nr_populated =0 , nr_account =0 ;if (unlikely (nr_pages <=0 ))return 0 ;
@@ -5079,15 +5080,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)
@@ -5097,6 +5090,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 6/6 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. On some architectures this is unnecessary and it confuses what the scope of the locking for per-cpu lists and VM counters are.
This patch reduces the scope of IRQs being disabled via local_[lock|unlock] and relies on preemption disabling for the per-cpu counters. This is not completely free on all architectures as architectures without HAVE_CMPXCHG_DOUBLE will disable/enable IRQs again for the mod_zone_freepage_state call. However, it clarifies what the per-cpu pages lock protects and how zone stats may need IRQs disabled if ever called from an IRQ context.
--- mm/page_alloc.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-)
mm/page_alloc.c +16 -6
@@ -3461,11 +3461,17 @@ 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) { +/* +* per-cpu counter updates are not preempt-safe but is +* acceptable to race versus interrupts. +*/ +preempt_disable ();__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 );zone_statistics (preferred_zone, zone,1 ); +preempt_enable (); } -local_unlock_irqrestore (&pagesets.lock, flags);return page; }
@@ -3517,15 +3523,17 @@ 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); +spin_unlock_irqrestore (&zone->lock, flags); +if (!page) goto failed; + +preempt_disable ();__mod_zone_freepage_state (zone, -(1 << order),get_pcppage_migratetype (page)); -__count_zid_vm_events (PGALLOC ,page_zonenum (page),1 << order);zone_statistics (preferred_zone, zone,1 ); -local_irq_restore (flags); +preempt_enable (); out:/* Separate test+clear to avoid unnecessary atomics */
@@ -5090,10 +5098,12 @@ unsigned long __alloc_pages_bulk(gfp_t gfp, int preferred_nid, nr_populated++; } +local_unlock_irqrestore (&pagesets.lock, flags); + +preempt_disable ();__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); +preempt_enable ();return nr_populated;
Signature · 1 line
2.26.2
Quoted 9 lines · On Mon, 29 Mar 2021 13:06:42 +0100 Mel Gorman <[email protected]>…
This series requires patches in Andrew's tree so the series is also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.gitmm-percpu-local_lock-v1r15tldr: Jesper and Chuck, it would be nice to verify if this series helps
the allocation rate of the bulk page allocator. RT people, this *partially* addresses some problemsPREEMPT_RThas with the page allocator but it needs review.
I've run a new micro-benchmark[1] which shows:
(CPU: Intel(R) Xeon(R) CPU E5-1650 v4 @ 3.60GHz)
BASELINE
single_page alloc+put: 194 cycles(tsc) 54.106 ns
LIST variant: time_bulk_page_alloc_free_list: step=bulk size
Per elem: 200 cycles(tsc) 55.667 ns (step:1)
Per elem: 143 cycles(tsc) 39.755 ns (step:2)
Per elem: 132 cycles(tsc) 36.758 ns (step:3)
Per elem: 128 cycles(tsc) 35.795 ns (step:4)
Per elem: 123 cycles(tsc) 34.339 ns (step:8)
Per elem: 120 cycles(tsc) 33.396 ns (step:16)
Per elem: 118 cycles(tsc) 32.806 ns (step:32)
Per elem: 115 cycles(tsc) 32.169 ns (step:64)
Per elem: 116 cycles(tsc) 32.258 ns (step:128)
ARRAY variant: time_bulk_page_alloc_free_array: step=bulk size
Per elem: 195 cycles(tsc) 54.225 ns (step:1)
Per elem: 127 cycles(tsc) 35.492 ns (step:2)
Per elem: 117 cycles(tsc) 32.643 ns (step:3)
Per elem: 111 cycles(tsc) 30.992 ns (step:4)
Per elem: 106 cycles(tsc) 29.606 ns (step:8)
Per elem: 102 cycles(tsc) 28.532 ns (step:16)
Per elem: 99 cycles(tsc) 27.728 ns (step:32)
Per elem: 98 cycles(tsc) 27.252 ns (step:64)
Per elem: 97 cycles(tsc) 27.090 ns (step:128)
This should be seen in comparison with the older micro-benchmark[2] done on branch mm-bulk-rebase-v5r9.
BASELINE
single_page alloc+put: Per elem: 199 cycles(tsc) 55.472 ns
LIST variant: time_bulk_page_alloc_free_list: step=bulk size
Per elem: 206 cycles(tsc) 57.478 ns (step:1)
Per elem: 154 cycles(tsc) 42.861 ns (step:2)
Per elem: 145 cycles(tsc) 40.536 ns (step:3)
Per elem: 142 cycles(tsc) 39.477 ns (step:4)
Per elem: 142 cycles(tsc) 39.610 ns (step:8)
Per elem: 137 cycles(tsc) 38.155 ns (step:16)
Per elem: 135 cycles(tsc) 37.739 ns (step:32)
Per elem: 134 cycles(tsc) 37.282 ns (step:64)
Per elem: 133 cycles(tsc) 36.993 ns (step:128)
ARRAY variant: time_bulk_page_alloc_free_array: step=bulk size
Per elem: 202 cycles(tsc) 56.383 ns (step:1)
Per elem: 144 cycles(tsc) 40.047 ns (step:2)
Per elem: 134 cycles(tsc) 37.339 ns (step:3)
Per elem: 128 cycles(tsc) 35.578 ns (step:4)
Per elem: 120 cycles(tsc) 33.592 ns (step:8)
Per elem: 116 cycles(tsc) 32.362 ns (step:16)
Per elem: 113 cycles(tsc) 31.476 ns (step:32)
Per elem: 110 cycles(tsc) 30.633 ns (step:64)
Per elem: 110 cycles(tsc) 30.596 ns (step:128)
This new patchset does show some improvements in the micro-benchmark.
Quoted 33 lines
The PCP (per-cpu page allocator in
page_alloc.c) share locking requirements with vmstat which is inconvenient and causes some issues. Possibly because of that, 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. The series splits that structure and separates the locking.Second,
PREEMPT_RTconsiders the following sequence to be unsafe as documented inDocumentation/locking/locktypes.rstlocal_irq_disable(); spin_lock(&lock);The pcp allocator has this sequence for
rmqueue_pcplist(local_irq_save) ->__rmqueue_pcplist->rmqueue_bulk(spin_lock). This series explicitly separates the locking requirements for the PCP list (local_lock) and stat updates (irqs disabled). Once that is done, the length of time IRQs are disabled can be reduced and in some cases, IRQ disabling can be replaced withpreempt_disable.After that, it was very obvious that
zone_statisticsin particular has way too much overhead and leaves IRQs disabled for longer than necessary. It has perfectly accurate counters requiring IRQs be disabled for parallel RMW sequences when inaccurate ones likevm_eventswould do. The series makes the NUMA statistics (NUMA_HITand friends) inaccurate counters that only require preempt be disabled.Finally the bulk page allocator can then do all the stat updates in bulk with IRQs enabled which should improve the efficiency of the bulk page allocator. Technically, this could have been done without the
local_lockand vmstat conversion work and the order simply reflects the timing of when different series were implemented.
Quoted 5 lines
No performance data is included because despite the overhead of the stats, it's within the noise for most workloads but Jesper and Chuck may observe a significant different with the same tests used for the bulk page allocator. The series is more likely to be interesting to the RT folk in terms of slowing getting the PREEMPT tree into mainline.
I've try to run some longer packet benchmarks later. A quick test showed performance was within same range, and slightly better. The perf report and objdump, did reveal that code layout prefers the label "failed:" as the primary code path, which should only be used for single page allocs, which is kind of weird. (But as performance is the same or slightly better, I will not complain).
Quoted 7 lines
drivers/base/node.c | 18 +-- include/linux/mmzone.h | 29 +++-- include/linux/vmstat.h | 65 ++++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 173 ++++++++++++++++------------ mm/vmstat.c | 254 +++++++++++++++-------------------------- 6 files changed, 254 insertions(+), 287 deletions(-)
Signature · 4 lines
Best regards, Jesper Dangaard Brouer MSc.CS, Principal Kernel Engineer at Red Hat LinkedIn: http://www.linkedin.com/in/brouer
Quoted 40 lines · Jesper Dangaard Brouer
Quoted 9 lines · On Mon, 29 Mar 2021 13:06:42 +0100 Mel Gorman <[email protected]>…
This series requires patches in Andrew's tree so the series is also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.gitmm-percpu-local_lock-v1r15tldr: Jesper and Chuck, it would be nice to verify if this series helps
the allocation rate of the bulk page allocator. RT people, this *partially* addresses some problemsPREEMPT_RThas with the page allocator but it needs review.I've run a new micro-benchmark[1] which shows:
(CPU: Intel(R) Xeon(R) CPU E5-1650 v4 @ 3.60GHz)<Editting to focus on arrays>
BASELINE
single_pagealloc+put: 194 cycles(tsc) 54.106 nsARRAY variant:
time_bulk_page_alloc_free_array: step=bulk sizePer elem: 195 cycles(tsc) 54.225 ns (step:1)
Per elem: 127 cycles(tsc) 35.492 ns (step:2)
Per elem: 117 cycles(tsc) 32.643 ns (step:3)
Per elem: 111 cycles(tsc) 30.992 ns (step:4)
Per elem: 106 cycles(tsc) 29.606 ns (step:8)
Per elem: 102 cycles(tsc) 28.532 ns (step:16)
Per elem: 99 cycles(tsc) 27.728 ns (step:32)
Per elem: 98 cycles(tsc) 27.252 ns (step:64)
Per elem: 97 cycles(tsc) 27.090 ns (step:128)This should be seen in comparison with the older micro-benchmark[2] done on branch mm-bulk-rebase-v5r9.
BASELINE
single_pagealloc+put: Per elem: 199 cycles(tsc) 55.472 nsARRAY variant:
time_bulk_page_alloc_free_array: step=bulk size
Quoted 10 lines
Per elem: 202 cycles(tsc) 56.383 ns (step:1)
Per elem: 144 cycles(tsc) 40.047 ns (step:2)
Per elem: 134 cycles(tsc) 37.339 ns (step:3)
Per elem: 128 cycles(tsc) 35.578 ns (step:4)
Per elem: 120 cycles(tsc) 33.592 ns (step:8)
Per elem: 116 cycles(tsc) 32.362 ns (step:16)
Per elem: 113 cycles(tsc) 31.476 ns (step:32)
Per elem: 110 cycles(tsc) 30.633 ns (step:64)
Per elem: 110 cycles(tsc) 30.596 ns (step:128)
Ok, so bulk allocation is faster than allocating single pages, no surprise there. Putting the array figures for bulk allocation into tabular format and comparing we get;
Array variant (time to allocate a page in nanoseconds, lower is better)
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%)
The series is 11-12% faster when allocating multiple pages. That's a fairly positive outcome and I'll include this in the series leader if you have no objections.
Thanks Jesper!
Signature · 2 lines
Mel Gorman SUSE Labs
Quoted 68 lines · On Wed, 31 Mar 2021 08:38:05 +0100 Mel Gorman <[email protected]>…
Quoted 40 lines · Jesper Dangaard Brouer
Quoted 9 lines · On Mon, 29 Mar 2021 13:06:42 +0100 Mel Gorman <[email protected]>…
This series requires patches in Andrew's tree so the series is also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.gitmm-percpu-local_lock-v1r15tldr: Jesper and Chuck, it would be nice to verify if this series helps
the allocation rate of the bulk page allocator. RT people, this *partially* addresses some problemsPREEMPT_RThas with the page allocator but it needs review.I've run a new micro-benchmark[1] which shows:
(CPU: Intel(R) Xeon(R) CPU E5-1650 v4 @ 3.60GHz)<Editting to focus on arrays>
BASELINE
single_pagealloc+put: 194 cycles(tsc) 54.106 nsARRAY variant:
time_bulk_page_alloc_free_array: step=bulk sizePer elem: 195 cycles(tsc) 54.225 ns (step:1)
Per elem: 127 cycles(tsc) 35.492 ns (step:2)
Per elem: 117 cycles(tsc) 32.643 ns (step:3)
Per elem: 111 cycles(tsc) 30.992 ns (step:4)
Per elem: 106 cycles(tsc) 29.606 ns (step:8)
Per elem: 102 cycles(tsc) 28.532 ns (step:16)
Per elem: 99 cycles(tsc) 27.728 ns (step:32)
Per elem: 98 cycles(tsc) 27.252 ns (step:64)
Per elem: 97 cycles(tsc) 27.090 ns (step:128)This should be seen in comparison with the older micro-benchmark[2] done on branch mm-bulk-rebase-v5r9.
BASELINE
single_pagealloc+put: Per elem: 199 cycles(tsc) 55.472 nsARRAY variant:
time_bulk_page_alloc_free_array: step=bulk sizeQuoted 10 lines
Per elem: 202 cycles(tsc) 56.383 ns (step:1)
Per elem: 144 cycles(tsc) 40.047 ns (step:2)
Per elem: 134 cycles(tsc) 37.339 ns (step:3)
Per elem: 128 cycles(tsc) 35.578 ns (step:4)
Per elem: 120 cycles(tsc) 33.592 ns (step:8)
Per elem: 116 cycles(tsc) 32.362 ns (step:16)
Per elem: 113 cycles(tsc) 31.476 ns (step:32)
Per elem: 110 cycles(tsc) 30.633 ns (step:64)
Per elem: 110 cycles(tsc) 30.596 ns (step:128)Ok, so bulk allocation is faster than allocating single pages, no surprise there. Putting the array figures for bulk allocation into tabular format and comparing we get;
Array variant (time to allocate a page in nanoseconds, lower is better)
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%)
Quoted 3 lines
The series is 11-12% faster when allocating multiple pages. That's a fairly positive outcome and I'll include this in the series leader if you have no objections.
That is fine by me to add this to the cover letter. I like your tabular format as it makes is easier to compare. If you use the nanosec measurements and not the cycles, you should state that this was run on a CPU E5-1650 v4 @ 3.60GHz. You might notice that the factor between cycles(tsc) and ns is very close to 3.6.
Signature · 4 lines
Best regards, Jesper Dangaard Brouer MSc.CS, Principal Kernel Engineer at Red Hat LinkedIn: http://www.linkedin.com/in/brouer
Ingo, Thomas or Peter, is there any chance one of you could take a look at patch "[PATCH 2/6] mm/page_alloc: Convert per-cpu list protection to local_lock" from this series? It's partially motivated by PREEMPT_RT. More details below.
Quoted 12 lines · Mel Gorman
This series requires patches in Andrew's tree so the series is also available at
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.gitmm-percpu-local_lock-v1r15The PCP (per-cpu page allocator in
page_alloc.c) share locking requirements with vmstat which is inconvenient and causes some issues. Possibly because of that, 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. The series splits that structure and separates the locking.
The bulk page allocation series that the local_lock work had an additional fix so I've rebased this onto
git://git.kernel.org/pub/scm/linux/kernel/git/mel/linux.git mm-percpu-local_lock-v1r16
Quoted 6 lines
Second,
PREEMPT_RTconsiders the following sequence to be unsafe as documented inDocumentation/locking/locktypes.rstlocal_irq_disable(); spin_lock(&lock);
Quoted 7 lines
The pcp allocator has this sequence for
rmqueue_pcplist(local_irq_save) ->__rmqueue_pcplist->rmqueue_bulk(spin_lock). This series explicitly separates the locking requirements for the PCP list (local_lock) and stat updates (irqs disabled). Once that is done, the length of time IRQs are disabled can be reduced and in some cases, IRQ disabling can be replaced withpreempt_disable.
It's this part I'm interested in even though it only partially addresses the preempt-rt tree concerns. More legwork is needed for preempt-rt which is outside the context of this series. At minimum, it involves
1. Split locking of pcp and buddy allocator instead of using spin_lock() when it's "known" that IRQs are disabled (not necessarily a valid assumption on PREEMPT_RT)
2. Split the zone lock into what protects the zone metadata and what protects the free lists
This looks straight-forward but it involves audit work and it may be difficult to avoid regressing non-PREEMPT_RT kernels by disabling/enabling IRQs when switching between the pcp allocator and the buddy allocator.
Quoted 30 lines
After that, it was very obvious that
zone_statisticsin particular has way too much overhead and leaves IRQs disabled for longer than necessary. It has perfectly accurate counters requiring IRQs be disabled for parallel RMW sequences when inaccurate ones likevm_eventswould do. The series makes the NUMA statistics (NUMA_HITand friends) inaccurate counters that only require preempt be disabled.Finally the bulk page allocator can then do all the stat updates in bulk with IRQs enabled which should improve the efficiency of the bulk page allocator. Technically, this could have been done without the
local_lockand vmstat conversion work and the order simply reflects the timing of when different series were implemented.No performance data is included because despite the overhead of the stats, it's within the noise for most workloads but Jesper and Chuck may observe a significant different with the same tests used for the bulk page allocator. The series is more likely to be interesting to the RT folk in terms of slowing getting the PREEMPT tree into mainline.
drivers/base/node.c | 18 +-- include/linux/mmzone.h | 29 +++-- include/linux/vmstat.h | 65 ++++++----- mm/mempolicy.c | 2 +- mm/page_alloc.c | 173 ++++++++++++++++------------ mm/vmstat.c | 254 +++++++++++++++-------------------------- 6 files changed, 254 insertions(+), 287 deletions(-)Signature · 1 line
2.26.2
Signature · 2 lines
Mel Gorman SUSE Labs
Quoted 4 lines · Mel Gorman
Ingo, Thomas or Peter, is there any chance one of you could take a look at patch "[PATCH 2/6]
mm/page_alloc: Convert per-cpu list protection tolocal_lock" from this series? It's partially motivated byPREEMPT_RT. More details below.
Sure.