mm: Proactive compaction
Subsystems (6)
PATCH v4 1/1 mm: Proactive compaction
For some applications, we need to allocate almost all memory as hugepages. However, on a running system, higher-order allocations can fail if the memory is fragmented. Linux kernel currently does on-demand compaction as we request more hugepages, but this style of compaction incurs very high latency. Experiments with one-time full memory compaction (followed by hugepage allocations) show that kernel is able to restore a highly fragmented memory state to a fairly compacted memory state within <1 sec for a 32G system. Such data suggests that a more proactive compaction can help us allocate a large fraction of memory as hugepages keeping allocation latencies low.
For a more proactive compaction, the approach taken here is to define a new tunable called 'proactiveness' which dictates bounds for external fragmentation wrt HUGETLB_PAGE_ORDER order which kcompactd tries to maintain.
The tunable is exposed through sysfs:
/sys/kernel/mm/compaction/proactiveness
It takes value in range [0, 100], with a default of 20.
Note that a previous version of this patch [1] was found to introduce too many tunables (per-order extfrag{low, high}), but this one reduces them to just one (proactiveness). Also, the new tunable is an opaque value instead of asking for specific bounds of "external fragmentation", which would have been difficult to estimate. The internal interpretation of this opaque value allows for future fine-tuning.
Currently, we use a simple translation from this tunable to [low, high] "fragmentation score" thresholds (low=100-proactiveness, high=low+10%). The score for a node is defined as weighted mean of per-zone external fragmentation wrt HUGETLB_PAGE_ORDER order. A zone's present_pages determines its weight.
To periodically check per-node score, we reuse per-node kcompactd threads, which are woken up every 500 milliseconds to check the same. If a node's score exceeds its high threshold (as derived from user-provided proactiveness value), proactive compaction is started until its score reaches its low threshold value. By default, proactiveness is set to 20, which implies threshold values of low=80 and high=90.
This patch is largely based on ideas from Michal Hocko posted here:
https://lore.kernel.org/linux-mm/[email protected]/
Performance data
================
System: x64_64, 1T RAM, 80 CPU threads.
Kernel: 5.6.0-rc3 + this patch
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
Before starting the driver, the system was fragmented from a userspace program that allocates all memory and then for each 2M aligned section, frees 3/4 of base pages using munmap. The workload is mainly anonymous userspace pages, which are easy to move around. I intentionally avoided unmovable pages in this test to see how much latency we incur when hugepage allocations hit direct compaction.
1. Kernel hugepage allocation latencies
With the system in such a fragmented state, a kernel driver then allocates as many hugepages as possible and measures allocation latency:
(all latency values are in microseconds)
- With vanilla 5.6.0-rc3
echo 0 | sudo tee /sys/kernel/mm/compaction/node-*/proactiveness
percentile latency
–––––––––– –––––––
5 7894 10 9496 25 12561 30 15295 40 18244 50 21229 60 27556 75 30147 80 31047 90 32859 95 33799
Total 2M hugepages allocated = 383859 (749G worth of hugepages out of 762G total free => 98% of free memory could be allocated as hugepages)
- With 5.6.0-rc3 + this patch, with proactiveness=20
echo 20 | sudo tee /sys/kernel/mm/compaction/node-*/proactiveness
percentile latency
–––––––––– –––––––
5 2 10 2 25 3 30 3 40 3 50 4 60 4 75 4 80 4 90 5 95 429
Total 2M hugepages allocated = 384105 (750G worth of hugepages out of 762G total free => 98% of free memory could be allocated as hugepages)
2. JAVA heap allocation
In this test, we first fragment memory using the same method as for (1).
Then, we start a Java process with a heap size set to 700G and request the heap to be allocated with THP hugepages. We also set THP to madvise to allow hugepage backing of this heap.
/usr/bin/time
java -Xms700G -Xmx700G -XX:+UseTransparentHugePages -XX:+AlwaysPreTouch
The above command allocates 700G of Java heap using hugepages.
- With vanilla 5.6.0-rc3
17.39user 1666.48system 27:37.89elapsed
- With 5.6.0-rc3 + this patch, with proactiveness=20
8.35user 194.58system 3:19.62elapsed
Elapsed time remains around 3:15, as proactiveness is further increased.
Note that proactive compaction happens throughout the runtime of these workloads. The situation of one-time compaction, sufficient to supply hugepages for following allocation stream, can probably happen for more extreme proactiveness values, like 80 or 90.
In the above Java workload, proactiveness is set to 20. The test starts with a node's score of 80 or higher, depending on the delay between the fragmentation step and starting the benchmark, which gives more-or-less time for the initial round of compaction. As the benchmark consumes hugepages, node's score quickly rises above the high threshold (90) and proactive compaction starts again, which brings down the score to the low threshold level (80). Repeat.
bpftrace also confirms proactive compaction running 20+ times during the runtime of this Java benchmark. kcompactd threads consume 100% of one of the CPUs while it tries to bring a node's score within thresholds.
Backoff behavior
================
Above workloads produce a memory state which is easy to compact. However, if memory is filled with unmovable pages, proactive compaction should essentially back off. To test this aspect:
- Created a kernel driver that allocates almost all memory as hugepages followed by freeing first 3/4 of each hugepage.
- Set proactiveness=40
- Note that proactive_compact_node() is deferred maximum number of times with HPAGE_FRAG_CHECK_INTERVAL_MSEC of wait between each check (=> ~30 seconds between retries).
[1] https://patchwork.kernel.org/patch/11098289/
To: Mel Gorman <[email protected]>
To: Michal Hocko <[email protected]>
To: Vlastimil Babka <[email protected]>
CC: Matthew Wilcox <[email protected]>
CC: Andrew Morton <[email protected]>
CC: Mike Kravetz <[email protected]>
CC: Joonsoo Kim <[email protected]>
CC: David Rientjes <[email protected]>
CC: Nitin Gupta <[email protected]>
CC: linux-kernel <[email protected]>
CC: linux-mm <[email protected]>
CC: Linux API <[email protected]>
---
Changelog v4 vs v3:
- Document various functions.
- Added admin-guide for the new tunable proactiveness.
- Rename proactive_compaction_score to fragmentation_score for clarity.
Changelog v3 vs v2:
- Make proactiveness a global tunable and not per-node. Also upadated the patch description to reflect the same (Vlastimil Babka).
- Don't start proactive compaction if kswapd is running (Vlastimil Babka).
- Clarified in the description that compaction runs in parallel with the workload, instead of a one-time compaction followed by a stream of hugepage allocations.
Changelog v2 vs v1:
- Introduce per-node and per-zone "proactive compaction score". This score is compared against watermarks which are set according to user provided proactiveness value.
- Separate code-paths for proactive compaction from targeted compaction i.e. where pgdat->kcompactd_max_order is non-zero.
- Renamed hpage_compaction_effort -> proactiveness. In future we may use more than extfrag wrt hugepage size to determine proactive compaction score.
--- .../admin-guide/mm/proactive-compaction.rst | 26 ++ MAINTAINERS | 6 + include/linux/compaction.h | 1 + mm/compaction.c | 236 +++++++++++++++++- mm/internal.h | 1 + mm/page_alloc.c | 1 + mm/vmstat.c | 17 ++ 7 files changed, 282 insertions(+), 6 deletions(-) create mode 100644 Documentation/admin-guide/mm/proactive-compaction.rst
Documentation/admin-guide/mm/proactive-compaction.rst new file +26 -0
@@ -0,0 +1,26 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. _proactive_compaction: + +==================== +Proactive Compaction +==================== + +Many applications benefit significantly from the use of huge pages. +However, huge-page allocations often incur a high latency or even fail +under fragmented memory conditions. Proactive compaction provides an +effective solution to these problems by doing memory compaction in the +background. + +The process of proactive compaction is controlled by a single tunable: + + /sys/kernel/mm/compaction/proactiveness + +This tunable takes a value in the range [0, 100] with a default value of +20. This tunable determines how aggressively compaction is done in the +background. Setting it to 0 disables proactive compaction. + +Note that compaction has a non-trivial system-wide impact as pages +belonging to different processes are moved around, which could also lead +to latency spikes in unsuspecting applications. The kernel employs +various heuristics to avoid wasting CPU cycles if it detects that +proactive compaction is not being effective.
MAINTAINERS +6 -0
@@ -18737,6 +18737,12 @@ L: [email protected] S: Maintained F: mm/zswap.c +PROACTIVE COMPACTION +M: Nitin Gupta <[email protected]> +L: [email protected] +S: Maintained +F: Documentation/admin-guide/mm/proactive-compaction.rst + THE REST M: Linus Torvalds <[email protected]> L: [email protected]
include/linux/compaction.h +1 -0
@@ -90,6 +90,7 @@ extern int sysctl_compaction_handler(struct ctl_table *table, int write,extern int sysctl_extfrag_threshold;extern int sysctl_compact_unevictable_allowed; +extern int extfrag_for_order (struct zone *zone,unsigned int order);extern int fragmentation_index (struct zone *zone,unsigned int order); extern enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order, unsigned int alloc_flags,
mm/compaction.c +230 -6
317 lines not on this page · raw message
mm/internal.h +1 -0
@@ -228,6 +228,7 @@ struct compact_control {bool no_set_skip_hint;/* Don't mark blocks for skipping */ bool ignore_block_suitable;/* Scan blocks considered unsuitable */ bool direct_compaction;/* False from kcompactd or /proc/... */ +bool proactive_compaction;/* kcompactd proactive compaction */ bool whole_zone;/* Whole zone should/has been scanned */ bool contended;/* Signal lock or sched contention */ bool rescan;/* Rescanning the same pageblock */
mm/page_alloc.c +1 -0
@@ -8479,6 +8479,7 @@ int alloc_contig_range(unsigned long start, unsigned long end, .no_set_skip_hint = true, .gfp_mask = current_gfp_context(gfp_mask), .alloc_contig = true, + .proactive_compaction = false, };INIT_LIST_HEAD (&cc.migratepages);
mm/vmstat.c +17 -0
@@ -1074,6 +1074,23 @@ static int __fragmentation_index(unsigned int order, struct contig_page_info *inreturn 1000 -div_u64 ( (1000 +(div_u64 (info->free_pages *1000ULL , requested))), info->free_blocks_total); } +/* +* Calculates external fragmentation within a zone wrt the given order. +* It is defined as the percentage of pages found in blocks of size +* less than 1 << order. It returns values in range [0, 100]. +*/ +int extfrag_for_order (struct zone *zone,unsigned int order) +{ +struct contig_page_info info; + +fill_contig_page_info (zone, order, &info); +if (info.free_pages ==0 ) +return 0 ; + +return (info.free_pages - (info.free_blocks_suitable << order)) *100 + / info.free_pages; +} +/* Same as __fragmentation index but allocs contig_page_info on stack */ int fragmentation_index (struct zone *zone,unsigned int order) {
Signature · 1 line
2.26.2
Quoted 16 lines · Nitin Gupta
For some applications, we need to allocate almost all memory as hugepages. However, on a running system, higher-order allocations can fail if the memory is fragmented. Linux kernel currently does on-demand compaction as we request more hugepages, but this style of compaction incurs very high latency. Experiments with one-time full memory compaction (followed by hugepage allocations) show that kernel is able to restore a highly fragmented memory state to a fairly compacted memory state within <1 sec for a 32G system. Such data suggests that a more proactive compaction can help us allocate a large fraction of memory as hugepages keeping allocation latencies low.
For a more proactive compaction, the approach taken here is to define a new tunable called 'proactiveness' which dictates bounds for external fragmentation wrt
HUGETLB_PAGE_ORDERorder which kcompactd tries to maintain.
Quoted 5 lines
The tunable is exposed through sysfs:
/sys/kernel/mm/compaction/proactiveness<snip>
Ping.
Any comments/feedback?
-Nitin
Quoted 11 lines · Nitin Gupta
For some applications, we need to allocate almost all memory as hugepages. However, on a running system, higher-order allocations can fail if the memory is fragmented. Linux kernel currently does on-demand compaction as we request more hugepages, but this style of compaction incurs very high latency. Experiments with one-time full memory compaction (followed by hugepage allocations) show that kernel is able to restore a highly fragmented memory state to a fairly compacted memory state within <1 sec for a 32G system. Such data suggests that a more proactive compaction can help us allocate a large fraction of memory as hugepages keeping allocation latencies low.
Quoted 7 lines
For a more proactive compaction, the approach taken here is to define a new tunable called 'proactiveness' which dictates bounds for external fragmentation wrt
HUGETLB_PAGE_ORDERorder which kcompactd tries to maintain.The tunable is exposed through sysfs:
/sys/kernel/mm/compaction/proactiveness
I would prefer sysctl. Why?
During the mm evolution we seem to have end up with stuff scattered over several places:
/proc/sys aka sysctl:/proc/sys/vm/compact_unevictable_allowed /proc/sys/vm/compact_memory - write-only one-time action trigger!
/sys/kernel/mm:
e.g. /sys/kernel/mm/transparent_hugepage/
This is unfortunate enough, and (influenced by my recent dive into sysctl perhaps :), I would have preferred sysctl only. In this case it's consistent that we have sysctls for compaction already, while this introduces a whole new compaction directory in the /sys/kernel/mm/ space.
Quoted 103 lines
It takes value in range [0, 100], with a default of 20.
Note that a previous version of this patch [1] was found to introduce too many tunables (per-order extfrag{low, high}), but this one reduces them to just one (proactiveness). Also, the new tunable is an opaque value instead of asking for specific bounds of "external fragmentation", which would have been difficult to estimate. The internal interpretation of this opaque value allows for future fine-tuning.
Currently, we use a simple translation from this tunable to [low, high] "fragmentation score" thresholds (low=100-proactiveness, high=low+10%). The score for a node is defined as weighted mean of per-zone external fragmentation wrt
HUGETLB_PAGE_ORDERorder. A zone'spresent_pagesdetermines its weight.To periodically check per-node score, we reuse per-node kcompactd threads, which are woken up every 500 milliseconds to check the same. If a node's score exceeds its high threshold (as derived from user-provided proactiveness value), proactive compaction is started until its score reaches its low threshold value. By default, proactiveness is set to 20, which implies threshold values of low=80 and high=90.
This patch is largely based on ideas from Michal Hocko posted here:
https://lore.kernel.org/linux-mm/[email protected]/Performance data
================System:
x64_64, 1T RAM, 80 CPU threads.
Kernel: 5.6.0-rc3 + this patchecho madvise | sudo tee
/sys/kernel/mm/transparent_hugepage/enabledecho madvise | sudo tee/sys/kernel/mm/transparent_hugepage/defragBefore starting the driver, the system was fragmented from a userspace program that allocates all memory and then for each 2M aligned section, frees 3/4 of base pages using munmap. The workload is mainly anonymous userspace pages, which are easy to move around. I intentionally avoided unmovable pages in this test to see how much latency we incur when hugepage allocations hit direct compaction.
1. Kernel hugepage allocation latencies
With the system in such a fragmented state, a kernel driver then allocates as many hugepages as possible and measures allocation latency:
(all latency values are in microseconds)
- With vanilla 5.6.0-rc3
echo 0 | sudo tee
/sys/kernel/mm/compaction/node-*/proactivenesspercentile latency
–––––––––– –––––––5 7894 10 9496 25 12561 30 15295 40 18244 50 21229 60 27556 75 30147 80 31047 90 32859 95 33799Total 2M hugepages allocated = 383859 (749G worth of hugepages out of 762G total free => 98% of free memory could be allocated as hugepages)
- With 5.6.0-rc3 + this patch, with proactiveness=20
echo 20 | sudo tee
/sys/kernel/mm/compaction/node-*/proactivenesspercentile latency
–––––––––– –––––––5 2 10 2 25 3 30 3 40 3 50 4 60 4 75 4 80 4 90 5 95 429Total 2M hugepages allocated = 384105 (750G worth of hugepages out of 762G total free => 98% of free memory could be allocated as hugepages)
2. JAVA heap allocation
In this test, we first fragment memory using the same method as for (1).
Then, we start a Java process with a heap size set to 700G and request the heap to be allocated with THP hugepages. We also set THP to madvise to allow hugepage backing of this heap.
/usr/bin/time
java -Xms700G -Xmx700G -XX:+UseTransparentHugePages -XX:+AlwaysPreTouchThe above command allocates 700G of Java heap using hugepages.
Quoted 7 lines
- With vanilla 5.6.0-rc3
17.39user 1666.48system 27:37.89elapsed
- With 5.6.0-rc3 + this patch, with proactiveness=20
8.35user 194.58system 3:19.62elapsed
I still wonder how the single additional CPU during compaction resulted in such an improvement. Isn't this against the Amdahl's law? :)
Quoted 33 lines
Elapsed time remains around 3:15, as proactiveness is further increased.
Note that proactive compaction happens throughout the runtime of these workloads. The situation of one-time compaction, sufficient to supply hugepages for following allocation stream, can probably happen for more extreme proactiveness values, like 80 or 90.
In the above Java workload, proactiveness is set to 20. The test starts with a node's score of 80 or higher, depending on the delay between the fragmentation step and starting the benchmark, which gives more-or-less time for the initial round of compaction. As the benchmark consumes hugepages, node's score quickly rises above the high threshold (90) and proactive compaction starts again, which brings down the score to the low threshold level (80). Repeat.
bpftrace also confirms proactive compaction running 20+ times during the runtime of this Java benchmark. kcompactd threads consume 100% of one of the CPUs while it tries to bring a node's score within thresholds.
Backoff behavior
================Above workloads produce a memory state which is easy to compact. However, if memory is filled with unmovable pages, proactive compaction should essentially back off. To test this aspect:
- Created a kernel driver that allocates almost all memory as hugepages followed by freeing first 3/4 of each hugepage.
- Set proactiveness=40
- Note thatproactive_compact_node()is deferred maximum number of times withHPAGE_FRAG_CHECK_INTERVAL_MSECof wait between each check (=> ~30 seconds between retries).
Quoted 4 lines
[1] https://patchwork.kernel.org/patch/11098289/
Signed-off-by Nitin Gupta <[email protected]>To: Mel Gorman <[email protected]>
I hope Mel can also comment on this, but in general I agree.
...
Quoted 7 lines
mm/compaction.c +7
-0+ +/* +* A zone's fragmentation score is the external fragmentation wrt to the +* HUGETLB_PAGE_ORDER scaled by the zone's size. It returns a value in the +* range [0, 100]. + +* The scaling factor ensures that proactive compaction focuses on larger
mm/compaction.c static inline bool is_via_compact_memory(int order) · line 1893
Quoted 10 lines
mm/compaction.c +10
-0+* zones like ZONE_NORMAL, rather than smaller, specialized zones like +* ZONE_DMA32. For smaller zones, the score value remains close to zero, +* and thus never exceeds the high threshold for proactive compaction. +*/ +static int fragmentation_score_zone (struct zone *zone) +{ +unsigned long score; + + score = zone->present_pages * +extfrag_for_order (zone,HUGETLB_PAGE_ORDER );
HPAGE_PMD_ORDER would be a better match than HUGETLB_PAGE_ORDER, even if it might be the same number. hugetlb pages are pre-reserved, unlike THP.
mm/compaction.c static inline bool is_via_compact_memory(int order) · line 1895
Quoted 2 lines
mm/compaction.c +2
-0+ score =div64_ul (score, +node_present_pages (zone->zone_pgdat->node_id) +1 );
zone->zone_pgdat->node_present_pages is more direct
mm/compaction.c static enum compact_result compact_zone_order(struct zone *zone, int order, · line 2414
Quoted 10 lines
mm/compaction.c +5
-0+return score; +} + +/* @@ -2309,6 +2411,7 @@ static enum compact_result compact_zone_order(struct zone *zone, int order, .alloc_flags = alloc_flags, .classzone_idx = classzone_idx, .direct_compaction = true, + .proactive_compaction = false,
false, 0, NULL etc are implicitly initialized with this kind of initialization (also in other places of the patch)
Quoted 24 lines
mm/compaction.c +12
-0.whole_zone = (prio == MIN_COMPACT_PRIORITY), .ignore_skip_hint = (prio ==MIN_COMPACT_PRIORITY ), .ignore_block_suitable = (prio == MIN_COMPACT_PRIORITY)@@ -2412,6 +2515,42 @@ enum compact_result try_to_compact_pages(gfp_t gfp_mask, unsigned int order,return rc; }@@ -2500,6 +2640,63 @@ void compaction_unregister_node(struct node *node) }#endif /* CONFIG_SYSFS && CONFIG_NUMA */ +#ifdef CONFIG_SYSFS + +#define COMPACTION_ATTR_RO (_name) \ + static struct kobj_attribute _name##_attr = __ATTR_RO(_name) + +#define COMPACTION_ATTR (_name) \ + static struct kobj_attribute _name##_attr = \ + __ATTR(_name, 0644, _name##_show, _name##_store) + +static struct kobject *compaction_kobj; + +static ssize_t proactiveness_store(struct kobject *kobj,
mm/compaction.c void compaction_unregister_node(struct node *node) · line 2664
Quoted 10 lines
mm/compaction.c +10
-0+ struct kobj_attribute *attr, const char *buf, size_t count) +{ + int err; +unsigned long input; + + err =kstrtoul (buf,10 , &input); +if (err) +return err; +if (input >100 ) +return -EINVAL ;
The sysctl way also allows to specify min/max in the descriptor and use the generic handler
Quoted 33 lines · Vlastimil Babka
Quoted 11 lines · Nitin Gupta
For some applications, we need to allocate almost all memory as hugepages. However, on a running system, higher-order allocations can fail if the memory is fragmented. Linux kernel currently does on-demand compaction as we request more hugepages, but this style of compaction incurs very high latency. Experiments with one-time full memory compaction (followed by hugepage allocations) show that kernel is able to restore a highly fragmented memory state to a fairly compacted memory state within <1 sec for a 32G system. Such data suggests that a more proactive compaction can help us allocate a large fraction of memory as hugepages keeping allocation latencies low.
Quoted 7 lines
For a more proactive compaction, the approach taken here is to define a new tunable called 'proactiveness' which dictates bounds for external fragmentation wrt
HUGETLB_PAGE_ORDERorder which kcompactd tries to maintain.The tunable is exposed through sysfs:
/sys/kernel/mm/compaction/proactivenessI would prefer sysctl. Why?
During the mm evolution we seem to have end up with stuff scattered over several
places:
/proc/sysaka sysctl:/proc/sys/vm/compact_unevictable_allowed/proc/sys/vm/compact_memory- write-only one-time action trigger!
/sys/kernel/mm:
e.g./sys/kernel/mm/transparent_hugepage/
Quoted 8 lines
This is unfortunate enough, and (influenced by my recent dive into sysctl perhaps :), I would have preferred sysctl only. In this case it's consistent
that we have sysctls for compaction already, while this introduces a whole new
compaction directory in the/sys/kernel/mm/space.
I have now replaced this sysfs node with vm.compaction_proactiveness sysctl.
Quoted 108 lines
Quoted 41 lines
It takes value in range [0, 100], with a default of 20.
Note that a previous version of this patch [1] was found to introduce too many tunables (per-order extfrag{low, high}), but this one reduces them to just one (proactiveness). Also, the new tunable is an opaque value instead of asking for specific bounds of "external fragmentation", which would have been difficult to estimate. The internal interpretation of this opaque value allows for future fine-tuning.
Currently, we use a simple translation from this tunable to [low, high] "fragmentation score" thresholds (low=100-proactiveness, high=low+10%). The score for a node is defined as weighted mean of per-zone external fragmentation wrt
HUGETLB_PAGE_ORDERorder. A zone'spresent_pagesdetermines its weight.To periodically check per-node score, we reuse per-node kcompactd threads, which are woken up every 500 milliseconds to check the same. If a node's score exceeds its high threshold (as derived from user-provided proactiveness value), proactive compaction is started until its score reaches its low threshold value. By default, proactiveness is set to 20, which implies threshold values of low=80 and high=90.
This patch is largely based on ideas from Michal Hocko posted here:
https://lore.kernel.org/linux-mm/[email protected]/Performance data
================System:
x64_64, 1T RAM, 80 CPU threads.
Kernel: 5.6.0-rc3 + this patchecho madvise | sudo tee
/sys/kernel/mm/transparent_hugepage/enabledecho madvise | sudo tee/sys/kernel/mm/transparent_hugepage/defragBefore starting the driver, the system was fragmented from a userspace program that allocates all memory and then for each 2M aligned section, frees 3/4 of base pages using munmap. The workload is mainly anonymous userspace pages, which are easy to move around. I intentionally avoided unmovable pages in this test to see how much latency we incur when hugepage allocations hit direct compaction.
Quoted 3 lines
1. Kernel hugepage allocation latencies
With the system in such a fragmented state, a kernel driver then
allocates
Quoted 63 lines
as many hugepages as possible and measures allocation latency:
(all latency values are in microseconds)
- With vanilla 5.6.0-rc3
echo 0 | sudo tee
/sys/kernel/mm/compaction/node-*/proactivenesspercentile latency
–––––––––– –––––––5 7894 10 9496 25 12561 30 15295 40 18244 50 21229 60 27556 75 30147 80 31047 90 32859 95 33799Total 2M hugepages allocated = 383859 (749G worth of hugepages out of 762G total free => 98% of free memory could be allocated as hugepages)
- With 5.6.0-rc3 + this patch, with proactiveness=20
echo 20 | sudo tee
/sys/kernel/mm/compaction/node-*/proactivenesspercentile latency
–––––––––– –––––––5 2 10 2 25 3 30 3 40 3 50 4 60 4 75 4 80 4 90 5 95 429Total 2M hugepages allocated = 384105 (750G worth of hugepages out of 762G total free => 98% of free memory could be allocated as hugepages)
2. JAVA heap allocation
In this test, we first fragment memory using the same method as for (1).
Then, we start a Java process with a heap size set to 700G and request the heap to be allocated with THP hugepages. We also set THP to madvise to allow hugepage backing of this heap.
/usr/bin/time
java -Xms700G -Xmx700G -XX:+UseTransparentHugePages -XX:+AlwaysPreTouchThe above command allocates 700G of Java heap using hugepages.
- With vanilla 5.6.0-rc3
17.39user 1666.48system 27:37.89elapsed
Quoted 9 lines
Quoted 3 lines
- With 5.6.0-rc3 + this patch, with proactiveness=20
8.35user 194.58system 3:19.62elapsed
I still wonder how the single additional CPU during compaction resulted in such
an improvement. Isn't this against the Amdahl's law? :)
The speedup is by avoiding the direct compaction path most of the time, so in
effect we are speeding up the "serial" part of user applications (back-to-back
memory allocations).
Quoted 51 lines
Quoted 33 lines
Elapsed time remains around 3:15, as proactiveness is further increased.
Note that proactive compaction happens throughout the runtime of these workloads. The situation of one-time compaction, sufficient to supply hugepages for following allocation stream, can probably happen for more extreme proactiveness values, like 80 or 90.
In the above Java workload, proactiveness is set to 20. The test starts with a node's score of 80 or higher, depending on the delay between the fragmentation step and starting the benchmark, which gives more-or-less time for the initial round of compaction. As the benchmark consumes hugepages, node's score quickly rises above the high threshold (90) and proactive compaction starts again, which brings down the score to the low threshold level (80). Repeat.
bpftrace also confirms proactive compaction running 20+ times during the runtime of this Java benchmark. kcompactd threads consume 100% of one of the CPUs while it tries to bring a node's score within thresholds.
Backoff behavior
================Above workloads produce a memory state which is easy to compact. However, if memory is filled with unmovable pages, proactive compaction should essentially back off. To test this aspect:
- Created a kernel driver that allocates almost all memory as hugepages followed by freeing first 3/4 of each hugepage.
- Set proactiveness=40
- Note thatproactive_compact_node()is deferred maximum number of times withHPAGE_FRAG_CHECK_INTERVAL_MSECof wait between each check (=> ~30 seconds between retries).Quoted 4 lines
[1] https://patchwork.kernel.org/patch/11098289/
Signed-off-by Nitin Gupta <[email protected]>To: Mel Gorman <[email protected]>
I hope Mel can also comment on this, but in general I agree.
...
mm/compaction.c static inline bool is_via_compact_memory(int order)
Quoted 4 lines
mm/compaction.c +4
-0+ +/* +* A zone's fragmentation score is the external fragmentation wrt to the +* HUGETLB_PAGE_ORDER scaled by the zone's size. It returns a value in the
Quoted 3 lines
+ * range [0, 100].
+
+ * The scaling factor ensures that proactive compaction focuses onlarger
Quoted 15 lines
mm/compaction.c static inline bool is_via_compact_memory(int order) · line 1893
Quoted 10 lines
+ * zones like
ZONE_NORMAL, rather than smaller, specialized zones like
+ *ZONE_DMA32. For smaller zones, the score value remains close to zero,
+ * and thus never exceeds the high threshold for proactive compaction.
+ */mm/compaction.c +6
-0+static int fragmentation_score_zone (struct zone *zone) +{ +unsigned long score; + + score = zone->present_pages * +extfrag_for_order (zone,HUGETLB_PAGE_ORDER );
HPAGE_PMD_ORDERwould be a better match thanHUGETLB_PAGE_ORDER, even if it might be the same number. hugetlb pages are pre-reserved, unlike THP.
Ok, I will change to HPAGE_PMD_ORDER.
Quoted 6 lines
mm/compaction.c static inline bool is_via_compact_memory(int order) · line 1895
Quoted 2 lines
mm/compaction.c +2
-0+ score =div64_ul (score, +node_present_pages (zone->zone_pgdat->node_id) +1 );
zone->zone_pgdat->node_present_pagesis more direct
Ok.
Quoted 7 lines
+ return score;
mm/compaction.c static enum compact_result
Quoted 5 lines
mm/compaction.c +3
-0+} + +/* @@ -2309,6 +2411,7 @@ static enum compact_resultcompact_zone_order(struct zone *zone, int order,
Quoted 10 lines
mm/compaction.c static enum compact_result compact_zone_order(struct zone *zone, int order, · line 2414
Quoted 4 lines
mm/compaction.c +1
-0.alloc_flags = alloc_flags, .classzone_idx = classzone_idx, .direct_compaction = true, + .proactive_compaction = false,false, 0, NULL etc are implicitly initialized with this kind of initialization
(also in other places of the patch)
hmm.. will remove these redundant initializations.
Quoted 30 lines
mm/compaction.c enum compact_result try_to_compact_pages(gfp_t
Quoted 4 lines
mm/compaction.c +0
-0.whole_zone = (prio == MIN_COMPACT_PRIORITY), .ignore_skip_hint = (prio ==MIN_COMPACT_PRIORITY ), .ignore_block_suitable = (prio == MIN_COMPACT_PRIORITY)@@ -2412,6 +2515,42 @@ enum compact_result try_to_compact_pages(gfp_t
gfp_mask, unsigned int order,Quoted 25 lines
mm/compaction.c +17
-0return rc; }@@ -2500,6 +2640,63 @@ void compaction_unregister_node(struct node *node) }#endif /* CONFIG_SYSFS && CONFIG_NUMA */ +#ifdef CONFIG_SYSFS + +#define COMPACTION_ATTR_RO (_name) \ + static struct kobj_attribute _name##_attr = __ATTR_RO(_name) + +#define COMPACTION_ATTR (_name) \ + static struct kobj_attribute _name##_attr = \ + __ATTR(_name, 0644, _name##_show, _name##_store) + +static struct kobject *compaction_kobj; + +static ssize_t proactiveness_store (struct kobject *kobj, +struct kobj_attribute *attr,const char *buf,size_t count) +{ +int err; +unsigned long input; +
Quoted 9 lines
mm/compaction.c void compaction_unregister_node(struct node *node) · line 2664
Quoted 5 lines
mm/compaction.c +5
-0+ err =kstrtoul (buf,10 , &input); +if (err) +return err; +if (input >100 ) +return -EINVAL ;The sysctl way also allows to specify min/max in the descriptor and use the generic handler
Thanks for pointing me to sysctl, it deletes ~50 lines from the patch :)
I will post v5 soon with the above changes.
Nitin