Fragmentation avoidance improvements
Subsystems (9)
cover Fragmentation avoidance improvements
Warning: This is a long intro with long changelogs and this is not a
trivial area to either analyse or fix. TLDR -- 95% reduction in fragmentation events, patches 1-3 should be relatively ok. Patch 4 and 5 need scrutiny but they are also independent or dropped.
It has been noted before that fragmentation avoidance (aka anti-fragmentation) is far from perfect. Given a long enough time or an adverse enough workload, memory still gets fragmented and the long-term success of high-order allocations degrades. This series defines an adverse workload, a definition of external fragmentation events (including serious) ones and a series that reduces the level of those fragmentation events.
This series is *not* directly related to the recent __GFP_THISNODE discussion and has no impact on the trivial test cases that were discussed there. This series was also evaluated without the candidate fixes from that discussion. The series does have consequences for high-order and THP allocations though that are important to consider so the same people are cc'd. It's also far from a complete solution but side-issues such as compaction, usability and other factors would require different series. It's also extremely important to note that this is analysed in the context of one adverse workload. While other patterns of fragmentation are possible (and workloads that are mostly slab allocations have a completely different solution space), they would need test cases to be properly considered.
The details of the workload and the consequences are described in more detail in the changelogs. However, from patch 1, this is a high-level summary of the adverse workload. The exact details are found in the mmtests implementation.
The broad details of the workload are as follows;
1. Create an XFS filesystem (not specified in the configuration but done as part of the testing for this patch)
2. Start 4 fio threads that write a number of 64K files inefficiently. Inefficiently means that files are created on first access and not created in advance (fio parameterr create_on_open=1) and fallocate is not used (fallocate=none). With multiple IO issuers this creates a mix of slab and page cache allocations over time. The total size of the files is 150% physical memory so that the slabs and page cache pages get mixed
3. Warm up a number of fio read-only threads accessing the same files created in step 2. This part runs for the same length of time it took to create the files. It'll fault back in old data and further interleave slab and page cache allocations. As it's now low on memory due to step 2, fragmentation occurs as pageblocks get stolen.
4. While step 3 is still running, start a process that tries to allocate 75% of memory as huge pages with a number of threads. The number of threads is based on a (NR_CPUS_SOCKET - NR_FIO_THREADS)/4 to avoid THP threads contending with fio, any other threads or forcing cross-NUMA scheduling. Note that the test has not been used on a machine with less than 8 cores. The benchmark records whether huge pages were allocated and what the fault latency was in microseconds
5. Measure the number of events potentially causing external fragmentation, the fault latency and the huge page allocation success rate.
6. Cleanup
Overall the series reduces external fragmentation causing events by over 95% on 1 and 2 socket machines, which in turn impacts high-order allocation success rates over the long term. There are differences in latencies and high-order allocation success rates. Latencies are a mixed bag as they are vulnerable to exact system state and whether allocations succeeded so they are treated as a secondary metric.
Patch 1 uses lower zones if they are populated and have free memory
instead of fragmenting a higher zone. It's special cased to handle a Normal->DMA32 fallback with the reasons explained in the changelog.
Patch 2+3 boosts watermarks temporarily when an external fragmentation
event occurs. kswapd wakes to reclaim a small amount of old memory and then wakes kcompactd on completion to recover the system slightly. This introduces some overhead in the slowpath. The level of boosting can be tuned or disabled depending on the tolerance for fragmentation vs allocation latency.
Patch 4 is more heavy handed. In the event of a movable allocation
request that can stall, it'll wake kswapd as in patch 3. However, if the expected fragmentation event is serious then the request will stall briefly on pfmemalloc_wait until kswapd completes light reclaim work and retry the allocation without stalling. This can avoid the fragmentation event entirely in some cases. The definition of a serious fragmentation event can be tuned or disabled.
Patch 5 is the hardest to prove it's a real benefit. In the event
that fragmentation was unavoidable, it'll queue a pageblock for kcompactd to clean. It's a fixed-length queue that is neither guaranteed to have a slot available or successfully clean a pageblock.
Patches 4 and 5 can be treated independently or dropped. The bulk of the improvement in fragmentation avoidance is from patches 1-3 (94-97% reduction in fragmentation events for an adverse workload on both a 1-socket and 2-socket machine).
Documentation/sysctl/vm.txt | 42 +++++++ include/linux/compaction.h | 4 + include/linux/migrate.h | 7 +- include/linux/mm.h | 2 + include/linux/mmzone.h | 18 ++- include/trace/events/compaction.h | 62 +++++++++++ kernel/sysctl.c | 18 +++ mm/compaction.c | 148 +++++++++++++++++++++++-- mm/internal.h | 14 ++- mm/migrate.c | 6 +- mm/page_alloc.c | 228 ++++++++++++++++++++++++++++++++++---- mm/vmscan.c | 123 ++++++++++++++++++-- 12 files changed, 621 insertions(+), 51 deletions(-)
Signature · 1 line
2.16.4
PATCH 1/5 mm, page_alloc: Spread allocations across zones before introducing fragmentation
The page allocator zone lists are iterated based on the watermarks of each zone which does not take anti-fragmentation into account. On x86, node 0 may have multiple zones while other nodes have one zone. A consequence is that tasks running on node 0 may fragment ZONE_NORMAL even though ZONE_DMA32 has plenty of free memory. This patch special cases the allocator fast path such that it'll try an allocation from a lower local zone before fragmenting a higher zone. In this case, stealing of pageblocks or orders larger than a pageblock are still allowed in the fast path as they are uninteresting from a fragmentation point of view.
This was evaluated using a benchmark designed to fragment memory before attempting THPs. It's implemented in mmtests as the following configurations
configs/config-global-dhp__workload_thpfioscale configs/config-global-dhp__workload_thpfioscale-defrag configs/config-global-dhp__workload_thpfioscale-madvhugepage
e.g. from mmtests
./run-mmtests.sh --run-monitor --config configs/config-global-dhp__workload_thpfioscale test-run-1
The broad details of the workload are as follows;
1. Create an XFS filesystem (not specified in the configuration but done as part of the testing for this patch)
2. Start 4 fio threads that write a number of 64K files inefficiently. Inefficiently means that files are created on first access and not created in advance (fio parameterr create_on_open=1) and fallocate is not used (fallocate=none). With multiple IO issuers this creates a mix of slab and page cache allocations over time. The total size of the files is 150% physical memory so that the slabs and page cache pages get mixed
3. Warm up a number of fio read-only threads accessing the same files created in step 2. This part runs for the same length of time it took to create the files. It'll fault back in old data and further interleave slab and page cache allocations. As it's now low on memory due to step 2, fragmentation occurs as pageblocks get stolen.
4. While step 3 is still running, start a process that tries to allocate 75% of memory as huge pages with a number of threads. The number of threads is based on a (NR_CPUS_SOCKET - NR_FIO_THREADS)/4 to avoid THP threads contending with fio, any other threads or forcing cross-NUMA scheduling. Note that the test has not been used on a machine with less than 8 cores. The benchmark records whether huge pages were allocated and what the fault latency was in microseconds
5. Measure the number of events potentially causing external fragmentation, the fault latency and the huge page allocation success rate.
6. Cleanup
Note that due to the use of IO and page cache that this benchmark is not suitable for running on large machines where the time to fragment memory may be excessive. Also note that while this is one mix that generates fragmentation that it's not the only mix that generates fragmentation. Differences in workload that are more slab-intensive or whether SLUB is used with high-order pages may yield different results.
When the page allocator fragments memory, it records the event using the mm_page_alloc_extfrag event. If the fallback_order is smaller than a pageblock order (order-9 on 64-bit x86) then it's considered an event that may cause external fragmentation issues in the future. Hence, the primary metric here is the number of external fragmentation events that occur with order < 9. The secondary metric is allocation latency and huge page allocation success rates but note that differences in latencies and what the success rate also can affect the number of external fragmentation event which is why it's a secondary metric.
1-socket Skylake machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 1 THP allocating thread
--------------------------------------
4.19 extfrag events < order 0: 71227
4.19+patch: 36456 (49% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
vanilla lowzone-v1r1
Amean fault-base-1 605.84 ( 0.00%) 599.92 * 0.98%*
Amean fault-huge-1 296.00 ( 0.00%) 179.84 * 39.24%* 4.19.0 4.19.0
vanilla lowzone-v1r1Percentage huge-1 0.44 ( 0.00%) 1.08 ( 146.15%)
Fault latencies are reduced. While allocation success rates are not much higher, this configuration does not make any heavy effort to allocate THP and fio is heavily active at the time and filling memory. However, a 49% reduction of serious fragmentation events reduces the changes of external fragmentation being a problem in the future.
1-socket Skylake machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 40761
4.19+patch: 36085 (11.47% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
vanilla lowzone-v1r1
Amean fault-base-1 1938.77 ( 0.00%) 1938.47 ( 0.02%)
Amean fault-huge-1 774.80 ( 0.00%) 749.40 * 3.28%*thpfioscale Percentage Faults Huge
4.19.0 4.19.0
vanilla lowzone-v1r1Percentage huge-1 83.59 ( 0.00%) 83.79 ( 0.24%)
Nothing dramatic. Fragmentation events are still reduced but the differences in fault latencies and allocation success rates are similar.
2-socket Haswell machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 5 THP allocating threads
----------------------------------------------------------------
4.19 extfrag events < order 0: 882868
4.19+patch: 476937 (46% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
vanilla lowzone-v1r1
Amean fault-base-5 1505.76 ( 0.00%) 1602.01 ( -6.39%)
Amean fault-huge-5 687.00 ( 0.00%) 0.00 * 100.00%* 4.19.0 4.19.0
vanilla lowzone-v1r1Percentage huge-5 0.07 ( 0.00%) 0.00 ( 0.00%)
The reduction of external fragmentation events is expected. The latencies are off because the huge page allocations generally failed and the patch does not have a direct impact on success rates.
2-socket Haswell machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 803099
4.19+patch: 654671 (23% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
vanilla lowzone-v1r1
Amean fault-base-5 5389.23 ( 0.00%) 6678.61 * -23.93%*
Amean fault-huge-5 5039.32 ( 0.00%) 2796.35 * 44.51%*thpfioscale Percentage Faults Huge
4.19.0 4.19.0
vanilla lowzone-v1r1Percentage huge-5 30.69 ( 0.00%) 57.92 ( 88.71%)
In this case, there was both a reduction in the external fragmentation causing events and the huge page allocation success rates were increased substantially from 30.69% of attempts to 57.92%.
Overall, the patch significantly reduces the number of external fragmentation causing events so the success of THP over long periods of time would be improved for this adverse workload.
--- mm/internal.h | 13 +++++--- mm/page_alloc.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 99 insertions(+), 15 deletions(-)
mm/internal.h +9 -4
@@ -480,10 +480,15 @@ unsigned long reclaim_clean_pages_from_list(struct zone *zone,#define ALLOC_OOM ALLOC_NO_WATERMARKS#endif -#define ALLOC_HARDER 0x10/* try to alloc harder */ -#define ALLOC_HIGH 0x20/* __GFP_HIGH set */ -#define ALLOC_CPUSET 0x40/* check for correct cpuset */ -#define ALLOC_CMA 0x80/* allow allocations from CMA areas */ +#define ALLOC_HARDER 0x10/* try to alloc harder */ +#define ALLOC_HIGH 0x20/* __GFP_HIGH set */ +#define ALLOC_CPUSET 0x40/* check for correct cpuset */ +#define ALLOC_CMA 0x80/* allow allocations from CMA areas */ +#ifdef CONFIG_ZONE_DMA32 +#define ALLOC_NOFRAGMENT 0x100/* avoid mixing pageblock types */ +#else +#define ALLOC_NOFRAGMENT 0x0 +#endif enum ttu_flags ;struct tlbflush_unmap_batch ;
mm/page_alloc.c +90 -11
213 lines not on this page · raw message
Signature · 1 line
2.16.4
PATCH 2/5 mm: Move zone watermark accesses behind an accessor
In reply to Mel GormanThis is a preparation patch only, no functional change.
--- include/linux/mmzone.h | 9 +++++---- mm/compaction.c | 2 +- mm/page_alloc.c | 12 ++++++------ 3 files changed, 12 insertions(+), 11 deletions(-)
include/linux/mmzone.h +5 -4
@@ -267,9 +267,10 @@ enum zone_watermarks {NR_WMARK }; -#define min_wmark_pages (z) (z->watermark[WMARK_MIN]) -#define low_wmark_pages (z) (z->watermark[WMARK_LOW]) -#define high_wmark_pages (z) (z->watermark[WMARK_HIGH]) +#define min_wmark_pages (z) (z->_watermark[WMARK_MIN]) +#define low_wmark_pages (z) (z->_watermark[WMARK_LOW]) +#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH]) +#define wmark_pages (z, i) (z->_watermark[i])struct per_cpu_pages {int count;/* number of pages in the list */
@@ -360,7 +361,7 @@ struct zone {/* Read-mostly fields */ /* zone watermarks, access with *_wmark_pages(zone) macros */ -unsigned long watermark[NR_WMARK ]; +unsigned long _watermark[NR_WMARK ];unsigned long nr_reserved_highatomic;
mm/compaction.c +1 -1
@@ -1430,7 +1430,7 @@ static enum compact_result __compaction_suitable(struct zone *zone, int order,if (is_via_compact_memory (order))return COMPACT_CONTINUE ; - watermark = zone->watermark[alloc_flags &ALLOC_WMARK_MASK ]; + watermark =wmark_pages (zone, alloc_flags &ALLOC_WMARK_MASK );/* * If watermarks for high-order allocation are already met, there * should be no need for compaction at all.
mm/page_alloc.c +6 -6
@@ -3360,7 +3360,7 @@ get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags, } } - mark = zone->watermark[alloc_flags &ALLOC_WMARK_MASK ]; + mark =wmark_pages (zone, alloc_flags &ALLOC_WMARK_MASK );if (!zone_watermark_fast (zone, order, mark,ac_classzone_idx (ac), alloc_flags)) {int ret;
@@ -4787,7 +4787,7 @@ long si_mem_available(void) pages[lru] =global_node_page_state (NR_LRU_BASE + lru);for_each_zone (zone) - wmark_low += zone->watermark[WMARK_LOW ]; + wmark_low +=low_wmark_pages (zone);/* * Estimate the amount of memory available for userspace allocations,
@@ -7323,13 +7323,13 @@ static void __setup_per_zone_wmarks(void) min_pages = zone->managed_pages /1024 ; min_pages =clamp (min_pages,SWAP_CLUSTER_MAX ,128UL ); - zone->watermark[WMARK_MIN ] = min_pages; + zone->_watermark[WMARK_MIN ] = min_pages; }else {/* * If it's a lowmem zone, reserve a number of pages * proportionate to the zone's size. */ - zone->watermark[WMARK_MIN ] = tmp; + zone->_watermark[WMARK_MIN ] = tmp; }/*
@@ -7341,8 +7341,8 @@ static void __setup_per_zone_wmarks(void)mult_frac (zone->managed_pages, watermark_scale_factor, 10000)); - zone->watermark[WMARK_LOW ] =min_wmark_pages (zone) + tmp; - zone->watermark[WMARK_HIGH ] =min_wmark_pages (zone) + tmp *2 ; + zone->_watermark[WMARK_LOW ] =min_wmark_pages (zone) + tmp; + zone->_watermark[WMARK_HIGH ] =min_wmark_pages (zone) + tmp *2 ;spin_unlock_irqrestore (&zone->lock, flags); }
Signature · 1 line
2.16.4
PATCH 3/5 mm: Reclaim small amounts of memory when an external fragmentation event occurs
In reply to Mel GormanAn external fragmentation event was previously described as
When the page allocator fragments memory, it records the event using the mm_page_alloc_extfrag event. If the fallback_order is smaller than a pageblock order (order-9 on 64-bit x86) then it's considered an event that will cause external fragmentation issues in the future.
The kernel reduces the probability of such events by increasing the watermark sizes by calling set_recommended_min_free_kbytes early in the lifetime of the system. This works reasonably well in general but if there is enough sparsely populated pageblocks then the problem can still occur as enough memory is free overall and kswapd stays asleep.
This patch introduces a watermark_boost_factor sysctl that allows a zone watermark to be temporarily boosted when an external fragmentation causing events occurs. The boosting will stall allocations below the boosted low watermark and kswapd is woken unconditionally to reclaim an amount of memory relative to the size of the high watermark and the watermark_boost_factor until the boost is cleared. When kswapd finishes, it wakes kcompactd at the pageblock order to clean some of the pageblocks that may have been affected by the fragmentation event. kswapd avoids any writeback or swap from reclaim context during this operation to avoid excessive system disruption in the name of fragmentation avoidance. Care is taken so that kswapd will do normal reclaim work if the system is really low on memory.
This was evaluated using the same workloads as "mm, page_alloc: Spread allocations across zones before introducing fragmentation".
1-socket Skylake machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 1 THP allocating thread
--------------------------------------
4.19 extfrag events < order 0: 71227
4.19+patch1: 36456 (49% reduction)
4.19+patch1-3: 4510 (94% reduction)
4.19.0 4.19.0
lowzone-v1r1 boost-v1r5
Amean fault-base-1 599.92 ( 0.00%) 630.44 * -5.09%*
Amean fault-huge-1 179.84 ( 0.00%) 179.22 ( 0.35%) 4.19.0 4.19.0
lowzone-v1r1 boost-v1r5Percentage huge-1 1.08 ( 0.00%) 2.89 ( 168.75%)
Note that external fragmentation causing events are massively reduced by this path whether in comparison to the previous kernel or the vanilla kernel. There is some jitter in the fault latencies and they are a bit more variable but the slight increase in THP allocation success rates would account for some of that.
1-socket Skylake machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 40761
4.19+patch1: 36085 (11% reduction)
4.19+patch1-3: 1887 (95% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
lowzone-v1r1 boost-v1r5
Amean fault-base-1 1938.47 ( 0.00%) 1863.70 * 3.86%*
Amean fault-huge-1 749.40 ( 0.00%) 776.07 * -3.56%*thpfioscale Percentage Faults Huge
4.19.0 4.19.0
lowzone-v1r1 boost-v1r5Percentage huge-1 83.79 ( 0.00%) 86.92 ( 3.73%)
As before, massive reduction in external fragmentation events, some jitter on latencies and a slight increase in THP allocation success rates.
2-socket Haswell machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 5 THP allocating threads
----------------------------------------------------------------
4.19 extfrag events < order 0: 882868
4.19+patch1: 476937 (46% reduction)
4.19+patch1-3: 29044 (97% reduction)
4.19.0 4.19.0
lowzone-v1r1 boost-v1r5
Amean fault-base-5 1602.01 ( 0.00%) 1595.28 ( 0.42%)
Amean fault-huge-5 0.00 ( 0.00%) 435.67 * -99.00%* 4.19.0 4.19.0
lowzone-v1r1 boost-v1r5Percentage huge-5 0.00 ( 0.00%) 0.15 ( 100.00%)
This is an illustration of why latencies are not the primary metric. There is a 97% reduction in fragmentation causing events but the huge page latencies are much higher because they went from never succeeding to a small success.
2-socket Haswell machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 803099
4.19+patch1: 654671 (23% reduction)
4.19+patch1-3: 24352 (97% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
lowzone-v1r1 boost-v1r5
Amean fault-base-5 6678.61 ( 0.00%) 5935.74 ( 11.12%)
Amean fault-huge-5 2796.35 ( 0.00%) 2611.69 ( 6.60%) 4.19.0 4.19.0
lowzone-v1r1 boost-v1r5Percentage huge-5 57.92 ( 0.00%) 66.18 ( 14.26%)
There is a large reduction in fragmentation events and is reflected by a higher THP allocation success rate without a negative impact on fault latencies.
--- Documentation/sysctl/vm.txt | 19 +++++++ include/linux/mm.h | 1 + include/linux/mmzone.h | 11 ++-- kernel/sysctl.c | 8 +++ mm/page_alloc.c | 50 +++++++++++++++++- mm/vmscan.c | 123 ++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 197 insertions(+), 15 deletions(-)
Documentation/sysctl/vm.txt +19 -0
@@ -63,6 +63,7 @@ files can be found in mm/swap.c. - swappiness - user_reserve_kbytes - vfs_cache_pressure +- watermark_boost_factor - watermark_scale_factor - zone_reclaim_mode
@@ -856,6 +857,24 @@ ten times more freeable objects than there are. ============================================================= +watermark_boost_factor: + +This factor controls the level of reclaim when memory is being fragmented. +It defines the percentage of the low watermark of a zone that will be +reclaimed if pages of different mobility are being mixed within pageblocks. +The intent is so that compaction has less work to do and increase the +success rate of future high-order allocations such as SLUB allocations, +THP and hugetlbfs pages. + +To make it sensible with respect to the matermark_scale_factor parameter, +the unit is in fractions of 10,000. The default value of 15000 means +that 150% of the high watermark will be reclaimed in the event of a +pageblock being mixed due to fragmentation. If this value is smaller +than a pageblock then a pageblocks worth of pages will be reclaimed (e.g. +2MB on 64-bit x86). A boost factor of 0 will disable the feature. + +============================================================= + watermark_scale_factor: This factor controls the aggressiveness of kswapd. It defines the
include/linux/mm.h +1 -0
@@ -2174,6 +2174,7 @@ extern void zone_pcp_reset(struct zone *zone);/* page_alloc.c */ extern int min_free_kbytes; +extern int watermark_boost_factor;extern int watermark_scale_factor;/* nommu.c */
include/linux/mmzone.h +7 -4
@@ -267,10 +267,10 @@ enum zone_watermarks {NR_WMARK }; -#define min_wmark_pages (z) (z->_watermark[WMARK_MIN]) -#define low_wmark_pages (z) (z->_watermark[WMARK_LOW]) -#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH]) -#define wmark_pages (z, i) (z->_watermark[i]) +#define min_wmark_pages (z) (z->_watermark[WMARK_MIN] + z->watermark_boost) +#define low_wmark_pages (z) (z->_watermark[WMARK_LOW] + z->watermark_boost) +#define high_wmark_pages (z) (z->_watermark[WMARK_HIGH] + z->watermark_boost) +#define wmark_pages (z, i) (z->_watermark[i] + z->watermark_boost)struct per_cpu_pages {int count;/* number of pages in the list */
@@ -362,6 +362,7 @@ struct zone {/* zone watermarks, access with *_wmark_pages(zone) macros */ unsigned long _watermark[NR_WMARK ]; +unsigned long watermark_boost;unsigned long nr_reserved_highatomic;
@@ -886,6 +887,8 @@ static inline int is_highmem(struct zone *zone)struct ctl_table ;int min_free_kbytes_sysctl_handler (struct ctl_table *,int ,void __user *,size_t *,loff_t *); +int watermark_boost_factor_sysctl_handler (struct ctl_table *,int , +void __user *,size_t *,loff_t *);int watermark_scale_factor_sysctl_handler (struct ctl_table *,int ,void __user *,size_t *,loff_t *);extern int sysctl_lowmem_reserve_ratio[MAX_NR_ZONES ];
kernel/sysctl.c +8 -0
@@ -1450,6 +1450,14 @@ static struct ctl_table vm_table[] = { .proc_handler = min_free_kbytes_sysctl_handler, .extra1 = &zero, }, + { + .procname = "watermark_boost_factor", + .data = &watermark_boost_factor, + .maxlen = sizeof(watermark_boost_factor), + .mode = 0644, + .proc_handler = watermark_boost_factor_sysctl_handler, + .extra1 = &zero, + }, { .procname = "watermark_scale_factor", .data = &watermark_scale_factor,
mm/page_alloc.c +48 -2
96 lines not on this page · raw message
mm/vmscan.c +114 -9
184 lines not on this page · raw message
Signature · 1 line
2.16.4
Re: [PATCH 3/5] mm: Reclaim small amounts of memory when an external fragmentation event occurs
Quoted 7 lines · Mel Gorman
An external fragmentation event was previously described as
When the page allocator fragments memory, it records the event using the
mm_page_alloc_extfragevent. If thefallback_orderis smaller than a pageblock order (order-9 on 64-bit x86) then it's considered an event that will cause external fragmentation issues in the future.
This had a build error reported by the 0-day bot. It's trivially fixed with
mm/page_alloc.c +4 -3
@@ -3317,8 +3317,8 @@ static bool zone_allows_reclaim(struct zone *local_zone, struct zone *zone)* probably too small. It only makes sense to spread allocations to avoid * fragmentation between the Normal and DMA32 zones. */ -static inline unsigned int alloc_flags_nofragment (struct zone *zone, -gfp_t gfp_mask) +static inline unsigned int +alloc_flags_nofragment (struct zone *zone,gfp_t gfp_mask) {if (zone_idx (zone) !=ZONE_NORMAL )return 0 ;
@@ -3340,7 +3340,8 @@ static inline unsigned int alloc_flags_nofragment(struct zone *zone, return ALLOC_NOFRAGMENT; }#else -static inline unsigned int alloc_flags_nofragment (struct zone *zone) +static inline unsigned int +alloc_flags_nofragment (struct zone *zone,gfp_t gfp_mask) {return 0 ; }
Signature · 2 lines
Mel Gorman SUSE Labs
PATCH 4/5 mm: Stall movable allocations until kswapd progresses during serious external fragmentation event
In reply to Mel GormanAn external fragmentation causing events as already been described. A serious external fragmentation causing event is described as one that steals a contiguous range of pages of an order lower than fragment_stall_order (PAGE_ALLOC_COSTLY_ORDER by default). If fragmentation would steal a block smaller than this, this patch causes a movable allocation request that is allowed to sleep to until kswapd makes progress. As kswapd has just been woken due to a boosted watermark, it's expected to return quickly.
This stall is not guaranteed to avoid serious fragmentation causing events. If memory pressure is high enough, the pages freed by kswapd may still be used or they may not be in pageblocks that contain only movable pages. Furthermore an allocation request that cannot stall (e.g. atomic allocations) or if for unmovable/reclaimable pages will still proceed without stalling.
1-socket Skylake machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 1 THP allocating thread
--------------------------------------
4.19 extfrag events < order 0: 71227
4.19+patch1: 36456 (49% reduction)
4.19+patch1-3: 4510 (94% reduction)
4.19+patch1-4: 548 (99% reduction)
Fragmentation events reduced further. The latency and allocation rates were similar so are not included for brevity.
1-socket Skylake machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 40761
4.19+patch1: 36085 (11% reduction)
4.19+patch1-3: 1887 (95% reduction)
4.19+patch1-4: 394 (99% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
boost-v1r5 stall-v1r6
Amean fault-base-1 1863.70 ( 0.00%) 3943.28 *-111.58%*
Amean fault-huge-1 776.07 ( 0.00%) 2739.80 *-253.03%* 4.19.0 4.19.0
boost-v1r5 stall-v1r6Percentage huge-1 86.92 ( 0.00%) 98.55 ( 13.39%)
Similar to the first case, the reduction in fragmentation events is notable. However, on this occasion the latencies are much higher but the allocation success rate is also way higher at 98% success rate. This is a case where the increased success rate causing pressure elsewhere but the reduced external framentation events means that compaction is more effective. This is a classic trade-off on whether allocation success rate is higher but if problematic, the behaviour can be tuned.
2-socket Haswell machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 5 THP allocating threads
----------------------------------------------------------------
4.19 extfrag events < order 0: 882868
4.19+patch1: 476937 (46% reduction)
4.19+patch1-3: 29044 (97% reduction)
4.19+patch1-4: 29290 (97% reduction)
There is little impact on fragmentation causing events but the latency and allocation rates were similar.
2-socket Haswell machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 803099
4.19+patch1: 654671 (23% reduction)
4.19+patch1-3: 24352 (97% reduction)
4.19+patch1-4: 16698 (98% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
boost-v1r5 stall-v1r6
Amean fault-base-5 5935.74 ( 0.00%) 8649.60 * -45.72%*
Amean fault-huge-5 2611.69 ( 0.00%) 2799.82 ( -7.20%) 4.19.0 4.19.0
boost-v1r5 stall-v1r6Percentage huge-5 66.18 ( 0.00%) 77.80 ( 17.56%)
Similar to the 1-socket case, the fragmentation events are reduced but the higher THP allocation success rates also impact the latencies as compaction goes to work.
This patch does reduce fragmentation rates overall but it's not free as some allocataions can stall for short periods of time. While it's within acceptable limits for the adverse test case, there may be other workloads that cannot tolerate the stalls. Either it can be tuned to disable the feature or more ideally, the test case is made available for analysis to see if the stall behaviour can be reduced while still limiting the fragmentation events. On the flip-side, it has been checked that setting the fragment_stall_order to 9 eliminated fragmentation events entirely on the 1-socket machine and by 99.71% on the 2-socket machine.
--- Documentation/sysctl/vm.txt | 23 +++++++++++++++ include/linux/mm.h | 1 + include/linux/mmzone.h | 2 ++ kernel/sysctl.c | 10 +++++++ mm/internal.h | 1 + mm/page_alloc.c | 68 +++++++++++++++++++++++++++++++++++++++------ 6 files changed, 97 insertions(+), 8 deletions(-)
Documentation/sysctl/vm.txt +23 -0
@@ -31,6 +31,7 @@ files can be found in mm/swap.c. - dirty_writeback_centisecs - drop_caches - extfrag_threshold +- fragment_stall_order - hugetlb_shm_group - laptop_mode - legacy_va_layout
@@ -275,6 +276,28 @@ any throttling. ============================================================== +fragment_stall_order + +External fragmentation control is managed on a pageblock level where the +page allocator tries to avoid mixing pages of different mobility within page +blocks (e.g. order 9 on 64-bit x86). If external fragmentation is perfectly +controlled then a THP allocation will often succeed up to the number of +movable pageblocks in the system as reported by /proc/pagetypeinfo. + +When memory is low, the system may have to mix pageblocks and will wake +kswapd to try control future fragmentation. fragment_stall_order controls if +the allocating task will stall if possible until kswapd makes some progress +in preference to fragmenting the system. This incurs a small stall penalty +in exchange for future success at allocating huge pages. If the stalls +are undesirable and high-order allocations are irrelevant then this can +be disabled by writing 0 to the tunable. Writing the pageblock order will +strongly (but not perfectly) control external fragmentation. + +The default will stall for fragmenting allocations smaller than the +PAGE_ALLOC_COSTLY_ORDER (defined as order-3 at the time of writing). + +============================================================== + hugetlb_shm_group hugetlb_shm_group contains group id that is allowed to create SysV
include/linux/mm.h +1 -0
@@ -2176,6 +2176,7 @@ extern void zone_pcp_reset(struct zone *zone);extern int min_free_kbytes;extern int watermark_boost_factor;extern int watermark_scale_factor; +extern int fragment_stall_order;/* nommu.c */ extern atomic_long_t mmap_pages_allocated;
include/linux/mmzone.h +2 -0
@@ -891,6 +891,8 @@ int watermark_boost_factor_sysctl_handler(struct ctl_table *, int, void __user *, size_t *, loff_t *);int watermark_scale_factor_sysctl_handler (struct ctl_table *,int ,void __user *,size_t *,loff_t *); +int fragment_stall_order_sysctl_handler (struct ctl_table *,int , +void __user *,size_t *,loff_t *);extern int sysctl_lowmem_reserve_ratio[MAX_NR_ZONES ];int lowmem_reserve_ratio_sysctl_handler (struct ctl_table *,int ,void __user *,size_t *,loff_t *);
kernel/sysctl.c +10 -0
@@ -125,6 +125,7 @@ static int zero;static int __maybe_unused one =1 ;static int __maybe_unused two =2 ;static int __maybe_unused four =4 ; +static int __maybe_unused max_order =MAX_ORDER ;static unsigned long one_ul =1 ;static int one_hundred =100 ;static int one_thousand =1000 ;
@@ -1467,6 +1468,15 @@ static struct ctl_table vm_table[] = { .extra1 = &one, .extra2 = &one_thousand, }, + { + .procname = "fragment_stall_order", + .data = &fragment_stall_order, + .maxlen = sizeof(fragment_stall_order), + .mode = 0644, + .proc_handler = fragment_stall_order_sysctl_handler, + .extra1 = &zero, + .extra2 = &max_order, + }, { .procname = "percpu_pagelist_fraction", .data = &percpu_pagelist_fraction,
mm/internal.h +1 -0
@@ -489,6 +489,7 @@ unsigned long reclaim_clean_pages_from_list(struct zone *zone,#else #define ALLOC_NOFRAGMENT 0x0#endif +#define ALLOC_FRAGMENT_STALL 0x200/* stall if fragmenting heavily */ enum ttu_flags ;struct tlbflush_unmap_batch ;
mm/page_alloc.c +60 -8
166 lines not on this page · raw message
Signature · 1 line
2.16.4
PATCH 5/5 mm: Target compaction on pageblocks that were recently fragmented
In reply to Mel GormanDespite the earlier patches, external fragmentation events are still inevitable as not all callers can stall or are appropriate to stall (e.g. unmovable allocations that kswapd reclaim will not necessarily help). In the event there is a mixed pageblock, it's desirable to move all movable pages from that block so that unmovable/unreclaimable allocations do not further pollute the address space.
This patch queues such pageblocks for early compaction and relies on kswapd to wake kcompactd when some pages are reclaimed. Waking kcompactd after kswapd makes progress is so that the compaction is more likely to have a suitable migration destination.
This patch may be controversial as there are multiple other design decisions that can be made. We could refuse to change pageblock ownership in some cases but great care would need to be taken to avoid premature OOMs or a livelock. Similarly, we could tag pageblocks as mixed and search for them but that would increase scanning costs. Finally, there is a corner case that a mixed pageblock that is after the point where a free scanner can operate may fail to clean the pageblock but addressing that would require a fundamental alteration to how compaction works.
Unlike the previous series, this one is harder to prove that it is a benefit because it ideally require a very long-lived workload that is fragmenting to show if it's really effective. The timing of such an allocation stream would be critical and detecting the change would be difficult can be within the noise. Hence, the potential benefit of this patch is more conceptual than quantitive even though there are some positive results.
1-socket Skylake machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 1 THP allocating thread
--------------------------------------
4.19 extfrag events < order 0: 71227
4.19+patch1: 36456 (49% reduction)
4.19+patch1-3: 4510 (94% reduction)
4.19+patch1-4: 548 (99% reduction)
4.19+patch1-5: 422 (99% reduction)
4.19.0 4.19.0
stall-v1r6 proactive-v1r6
Amean fault-base-1 839.48 ( 0.00%) 860.89 * -2.55%*
Amean fault-huge-1 172.74 ( 0.00%) 159.49 ( 7.67%) 4.19.0 4.19.0
stall-v1r6 proactive-v1r6Percentage huge-1 1.04 ( 0.00%) 2.29 ( 119.35%)
While there is an improvement in the reduction of fragmentation events and allocation success rates, the differences are marginal enough that it may not be significant.
1-socket Skylake machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 40761
4.19+patch1: 36085 (11% reduction)
4.19+patch1-3: 1887 (95% reduction)
4.19+patch1-4: 394 (99% reduction)
4.19+patch1-5: 440 (99% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
stall-v1r6 proactive-v1r6
Amean fault-base-1 3943.28 ( 0.00%) 2704.46 * 31.42%*
Amean fault-huge-1 2739.80 ( 0.00%) 2552.13 * 6.85%*thpfioscale Percentage Faults Huge
4.19.0 4.19.0
stall-v1r6 proactive-v1r6Percentage huge-1 98.55 ( 0.00%) 98.76 ( 0.20%)
Slight increase in fragmentation events albeit very small. The latency is much improved as well as a slight increase in allocation success rates but this may be a co-incidence of the system state.
2-socket Haswell machine config-global-dhp__workload_thpfioscale XFS (no special madvise) 4 fio threads, 5 THP allocating threads
----------------------------------------------------------------
4.19 extfrag events < order 0: 882868
4.19+patch1: 476937 (46% reduction)
4.19+patch1-3: 29044 (97% reduction) 4.19+patch1-4: 29290 (97% reduction) 4.19+patch1-5: 30791 (97% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
stall-v1r6 proactive-v1r6
Amean fault-base-5 1773.24 ( 0.00%) 1519.89 * 14.29%*
Amean fault-huge-5 17791.20 ( 0.00%) 536.44 ( 96.98%) 4.19.0 4.19.0
stall-v1r6 proactive-v1r6Percentage huge-5 0.17 ( 0.00%) 0.98 ( 490.00%)
Again, the fragmentation causing events is slightly increased although this is likely within the noise. The latency is massively improved but the success rate is only marginally improved. Given the low success rate, it may be a co-incidence of the exact system state during the test but the fact it happened on both 1 and 2 socket machines is encouraging.
2-socket Haswell machine global-dhp__workload_thpfioscale-madvhugepage-xfs (MADV_HUGEPAGE)
-----------------------------------------------------------------
4.19 extfrag events < order 0: 803099
4.19+patch1: 654671 (23% reduction)
4.19+patch1-3: 24352 (97% reduction) 4.19+patch1-4: 16698 (98% reduction) 4.19+patch1-5: 32623 (96% reduction)
thpfioscale Fault Latencies
4.19.0 4.19.0
stall-v1r6 proactive-v1r6
Amean fault-base-5 8649.60 ( 0.00%) 13074.71 * -51.16%*
Amean fault-huge-5 2799.82 ( 0.00%) 3410.02 * -21.79%*thpfioscale Percentage Faults Huge
4.19.0 4.19.0
stall-v1r6 proactive-v1r6Percentage huge-5 77.80 ( 0.00%) 83.30 ( 7.06%)
This shows an increase in both fragmentation events and latency. However it is somewhat balanced by the higher allocation success rates which in themselves can increase fragmentation pressure.
This is less an obvious universal win. It does control fragmentation better to some extent in that pageblocks can be found faster in some cases but the nature of the workload makes it less clear-cut.
--- include/linux/compaction.h | 4 ++ include/linux/migrate.h | 7 +- include/linux/mmzone.h | 4 ++ include/trace/events/compaction.h | 62 ++++++++++++++++ mm/compaction.c | 146 +++++++++++++++++++++++++++++++++++--- mm/migrate.c | 6 +- mm/page_alloc.c | 7 ++ 7 files changed, 225 insertions(+), 11 deletions(-)
include/linux/compaction.h +4 -0
@@ -177,6 +177,7 @@ bool compaction_zonelist_suitable(struct alloc_context *ac, int order,extern int kcompactd_run (int nid);extern void kcompactd_stop (int nid);extern void wakeup_kcompactd (pg_data_t *pgdat,int order,int classzone_idx); +extern void kcompactd_queue_migration (struct zone *zone,struct page *page);#else static inline void reset_isolation_suitable (pg_data_t *pgdat)
@@ -225,6 +226,9 @@ static inline void wakeup_kcompactd(pg_data_t *pgdat, int order, int classzone_i { } +static inline void kcompactd_queue_migration (struct zone *zone,struct page *page) +{ +}#endif /* CONFIG_COMPACTION */ #if defined(CONFIG_COMPACTION ) && defined(CONFIG_SYSFS ) && defined(CONFIG_NUMA )
include/linux/migrate.h +5 -2
@@ -61,7 +61,7 @@ static inline struct page *new_page_nodemask(struct page *page,#ifdef CONFIG_MIGRATION -extern void putback_movable_pages (struct list_head *l); +extern unsigned int putback_movable_pages (struct list_head *l);extern int migrate_page (struct address_space *mapping,struct page *newpage,struct page *page,enum migrate_mode mode);
@@ -82,7 +82,10 @@ extern int migrate_page_move_mapping(struct address_space *mapping, int extra_count);#else -static inline void putback_movable_pages (struct list_head *l) {} +static inline unsigned int putback_movable_pages (struct list_head *l) +{ +return 0 ; +}static inline int migrate_pages (struct list_head *l,new_page_t new,free_page_t free,unsigned long private,enum migrate_mode mode,int reason)
include/linux/mmzone.h +4 -0
@@ -495,6 +495,10 @@ struct zone {unsigned int compact_considered;unsigned int compact_defer_shift;int compact_order_failed; + +#define COMPACT_QUEUE_LENGTH 16 +unsigned long compact_queue[COMPACT_QUEUE_LENGTH ]; +int nr_compact;#endif #if definedCONFIG_COMPACTION || definedCONFIG_CMA
include/trace/events/compaction.h +62 -0
68 lines not on this page · raw message
mm/compaction.c +138 -8
194 lines not on this page · raw message
mm/migrate.c +5 -1
@@ -164,12 +164,14 @@ void putback_movable_page(struct page *page)* built from lru, balloon, hugetlbfs page. See isolate_migratepages_range() * and isolate_huge_page(). */ -void putback_movable_pages (struct list_head *l) +unsigned int putback_movable_pages (struct list_head *l) {struct page *page;struct page *page2; +unsigned int nr_putback =0 ;list_for_each_entry_safe (page, page2, l, lru) { + nr_putback++;if (unlikely (PageHuge (page))) {putback_active_hugepage (page);continue ;
@@ -195,6 +197,8 @@ void putback_movable_pages(struct list_head *l)putback_lru_page (page); } } + +return nr_putback; }/*
mm/page_alloc.c +7 -0
@@ -2190,6 +2190,9 @@ static bool steal_suitable_fallback(struct zone *zone, struct page *page,boost_watermark (zone);wakeup_kswapd (zone,0 ,0 ,zone_idx (zone)); +if (start_type ==MIGRATE_MOVABLE || old_block_type ==MIGRATE_MOVABLE ) +kcompactd_queue_migration (zone, page); +if ((alloc_flags &ALLOC_FRAGMENT_STALL ) && current_order < fragment_stall_order) {return false;
@@ -6359,7 +6362,11 @@ static void pgdat_init_split_queue(struct pglist_data *pgdat) {}#ifdef CONFIG_COMPACTION static void pgdat_init_kcompactd (struct pglist_data *pgdat) { +int i; +init_waitqueue_head (&pgdat->kcompactd_wait); +for (i =0 ; i <MAX_NR_ZONES ; i++) + pgdat->node_zones[i].nr_compact =0 ; }#else static void pgdat_init_kcompactd (struct pglist_data *pgdat) {}
Signature · 1 line
2.16.4