THP Shrinker
Subsystems (10)
RFC 1/3 mm: add thp_utilization metrics to debugfs
In reply to [email protected]From: Alexander Zhu <[email protected]>
This change introduces a tool that scans through all of physical memory for anonymous THPs and groups them into buckets based on utilization. It also includes an interface under /sys/kernel/debug/thp_utilization.
Sample Output:
Utilized[0-50]: 1331 680884
Utilized[51-101]: 9 3983
Utilized[102-152]: 3 1187
Utilized[153-203]: 0 0
Utilized[204-255]: 2 539
Utilized[256-306]: 5 1135
Utilized[307-357]: 1 192
Utilized[358-408]: 0 0
Utilized[409-459]: 1 57
Utilized[460-512]: 400 13
Last Scan Time: 223.98
Last Scan Duration: 70.65
This indicates that there are 1331 THPs that have between 0 and 50 utilized (non zero) pages. In total there are 680884 zero pages in this utilization bucket. THPs in the [0-50] bucket compose 76% of total THPs, and are responsible for 99% of total zero pages across all THPs. In other words, the least utilized THPs are responsible for almost all of the memory waste when THP is always enabled. Similar results have been observed across production workloads.
The last two lines indicate the timestamp and duration of the most recent scan through all of physical memory. Here we see that the last scan occurred 223.98 seconds after boot time and took 70.65 seconds.
Utilization of a THP is defined as the percentage of nonzero pages in the THP. The worker thread will scan through all of physical memory and obtain utilization of all anonymous THPs. It will gather this information by periodically scanning through all of physical memory for anonymous THPs, group them into buckets based on utilization, and report utilization information through debugfs under /sys/kernel/debug/thp_utilization.
--- Documentation/admin-guide/mm/transhuge.rst | 9 + include/linux/huge_mm.h | 2 + mm/huge_memory.c | 198 +++++++++++++++++++++ 3 files changed, 209 insertions(+)
Documentation/admin-guide/mm/transhuge.rst +9 -0
@@ -297,6 +297,15 @@ To identify what applications are mapping file transparent huge pages, it is necessary to read ``/proc/PID/smaps`` and count the FileHugeMapped fields for each mapping. +The utilization of transparent hugepages can be viewed by reading +``/sys/kernel/debug/thp_utilization``. The utilization of a THP is defined +as the ratio of non zero filled 4kb pages to the total number of pages in a +THP. The buckets are labelled by the range of total utilized 4kb pages with +one line per utilization bucket. Each line contains the total number of +THPs in that bucket and the total number of zero filled 4kb pages summed +over all THPs in that bucket. The last two lines show the timestamp and +duration respectively of the most recent scan over all of physical memory. + Note that reading the smaps file is expensive and reading it frequently will incur overhead.
include/linux/huge_mm.h +2 -0
@@ -179,6 +179,8 @@ bool hugepage_vma_check(struct vm_area_struct *vma,unsigned long thp_get_unmapped_area (struct file *filp,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags); +int thp_number_utilized_pages (struct page *page); +void prep_transhuge_page (struct page *page);void free_transhuge_page (struct page *page);
mm/huge_memory.c +198 -0
237 lines not on this page · raw message
Signature · 1 line
2.30.2
cover THP Shrinker
From: Alexander Zhu <[email protected]>
Transparent Hugepages use a larger page size of 2MB in comparison to normal sized pages that are 4kb. A larger page size allows for fewer TLB cache misses and thus more efficient use of the CPU. Using a larger page size also results in more memory waste, which can hurt performance in some use cases. THPs are currently enabled in the Linux Kernel by applications in limited virtual address ranges via the madvise system call. The THP shrinker tries to find a balance between increased use of THPs, and increased use of memory. It shrinks the size of memory by removing the underutilized THPs that are identified by the thp_utilization scanner.
In our experiments we have noticed that the least utilized THPs are almost entirely unutilized.
Sample Output:
Utilized[0-50]: 1331 680884
Utilized[51-101]: 9 3983
Utilized[102-152]: 3 1187
Utilized[153-203]: 0 0
Utilized[204-255]: 2 539
Utilized[256-306]: 5 1135
Utilized[307-357]: 1 192
Utilized[358-408]: 0 0
Utilized[409-459]: 1 57
Utilized[460-512]: 400 13
Last Scan Time: 223.98
Last Scan Duration: 70.65
Above is a sample obtained from one of our test machines when THP is always enabled. Of the 1331 THPs in this thp_utilization sample that have from 0-50 utilized subpages, we see that there are 680884 free pages. This comes out to 680884 / (512 * 1331) = 99.91% zero pages in the least utilized bucket. This represents 680884 * 4KB = 2.7GB memory waste.
Also note that the vast majority of pages are either in the least utilized [0-50] or most utilized [460-512] buckets. The least utilized THPs are responsible for almost all of the memory waste when THP is always enabled. Thus by clearing out THPs in the lowest utilization bucket we extract most of the improvement in CPU efficiency. We have seen similar results on our production hosts.
This patchset introduces the THP shrinker we have developed to identify and split the least utilized THPs. It includes the thp_utilization changes that groups anonymous THPs into buckets, the split_huge_page() changes that identify and zap zero 4KB pages within THPs and the shrinker changes. It should be noted that the split_huge_page() changes are based off previous work done by Yu Zhao.
In the future, we intend to allow additional tuning to the shrinker based on workload depending on CPU/IO/Memory pressure and the amount of anonymous memory. The long term goal is to eventually always enable THP for all applications and deprecate madvise entirely.
Alexander Zhu (3):
mm: add thp_utilization metrics to debugfs
mm: changes to split_huge_page() to free zero filled tail pages
mm: THP low utilization shrinker
Documentation/admin-guide/mm/transhuge.rst | 9 + include/linux/huge_mm.h | 9 + include/linux/list_lru.h | 24 ++ include/linux/mm_types.h | 5 + include/linux/rmap.h | 2 +- include/linux/vm_event_item.h | 2 + mm/huge_memory.c | 333 +++++++++++++++++- mm/list_lru.c | 49 +++ mm/migrate.c | 60 +++- mm/migrate_device.c | 4 +- mm/page_alloc.c | 6 + mm/vmstat.c | 2 + .../selftests/vm/split_huge_page_test.c | 58 ++- tools/testing/selftests/vm/vm_util.c | 23 ++ tools/testing/selftests/vm/vm_util.h | 1 + 15 files changed, 569 insertions(+), 18 deletions(-)
Signature · 1 line
2.30.2
RFC 3/3 mm: THP low utilization shrinker
From: Alexander Zhu <[email protected]>
This patch introduces a shrinker that will remove THPs in the lowest utilization bucket. As previously mentioned, we have observed that almost all of the memory waste when THPs are always enabled is contained in the lowest utilization bucket. The shrinker will add these THPs to a list_lru and split anonymous THPs based off information from kswapd. It requires the changes from thp_utilization to identify the least utilized THPs, and the changes to split_huge_page to identify and free zero pages within THPs.
--- include/linux/huge_mm.h | 7 +++ include/linux/list_lru.h | 24 +++++++++++ include/linux/mm_types.h | 5 +++ mm/huge_memory.c | 92 ++++++++++++++++++++++++++++++++++++++-- mm/list_lru.c | 49 +++++++++++++++++++++ mm/page_alloc.c | 6 +++ 6 files changed, 180 insertions(+), 3 deletions(-)
include/linux/huge_mm.h +7 -0
@@ -192,6 +192,8 @@ static inline int split_huge_page(struct page *page) }void deferred_split_huge_page (struct page *page); +void add_underutilized_thp (struct page *page); +void __split_huge_pmd (struct vm_area_struct *vma,pmd_t *pmd,unsigned long address,bool freeze,struct folio *folio);
@@ -302,6 +304,11 @@ static inline struct list_head *page_deferred_list(struct page *page) return &page[2].deferred_list; } +static inline struct list_head *page_underutilized_thp_list (struct page *page) +{ +return &page[3 ].underutilized_thp_list; +} +#else /* CONFIG_TRANSPARENT_HUGEPAGE */ #define HPAGE_PMD_SHIFT ({ BUILD_BUG(); 0; })#define HPAGE_PMD_MASK ({ BUILD_BUG(); 0; })
include/linux/list_lru.h +24 -0
@@ -89,6 +89,18 @@ void memcg_reparent_list_lrus(struct mem_cgroup *memcg, struct mem_cgroup *paren*/ bool list_lru_add (struct list_lru *lru,struct list_head *item); +/** +* list_lru_add_page: add an element to the lru list's tail +* @list_lru: the lru pointer +* @page: the page containing the item +* @item: the item to be deleted. +* +* This function works the same as list_lru_add in terms of list +* manipulation. Used for non slab objects contained in the page. +* +* Return value: true if the list was updated, false otherwise +*/ +bool list_lru_add_page (struct list_lru *lru,struct page *page,struct list_head *item);/** * list_lru_del: delete an element to the lru list * @list_lru: the lru pointer
@@ -102,6 +114,18 @@ bool list_lru_add(struct list_lru *lru, struct list_head *item);*/ bool list_lru_del (struct list_lru *lru,struct list_head *item); +/** +* list_lru_del_page: delete an element to the lru list +* @list_lru: the lru pointer +* @page: the page containing the item +* @item: the item to be deleted. +* +* This function works the same as list_lru_del in terms of list +* manipulation. Used for non slab objects contained in the page. +* +* Return value: true if the list was updated, false otherwise +*/ +bool list_lru_del_page (struct list_lru *lru,struct page *page,struct list_head *item);/** * list_lru_count_one: return the number of objects currently held by @lru * @lru: the lru pointer.
include/linux/mm_types.h +5 -0
@@ -151,6 +151,11 @@ struct page {/* For both global and memcg */ struct list_head deferred_list; }; +struct {/* Third tail page of compound page */ +unsigned long _compound_pad_3;/* compound_head */ +unsigned long _compound_pad_4; +struct list_head underutilized_thp_list; + };struct {/* Page table pages */ unsigned long _pt_pad_1;/* compound_head */ pgtable_t pmd_huge_pte;/* protected by page->ptl */
mm/huge_memory.c +89 -3
181 lines not on this page · raw message
mm/list_lru.c +49 -0
61 lines not on this page · raw message
mm/page_alloc.c +6 -0
@@ -1327,6 +1327,12 @@ static int free_tail_pages_check(struct page *head_page, struct page *page)* deferred_list.next -- ignore value. */ break ; +case 3 : +/* +* the third tail page: ->mapping is +* underutilized_thp_list.next -- ignore value. +*/ +break ;default :if (page->mapping !=TAIL_MAPPING ) {bad_page (page,"corrupted mapping in tail page" );
Signature · 1 line
2.30.2
RFC 2/3 mm: changes to split_huge_page() to free zero filled tail pages
In reply to [email protected]From: Alexander Zhu <[email protected]>
Currently, when /sys/kernel/mm/transparent_hugepage/enabled=always is set there are a large number of transparent hugepages that are almost entirely zero filled. This is mentioned in a number of previous patchsets including:
https://lore.kernel.org/all/[email protected]/
https://lore.kernel.org/all/
[email protected]/
Currently, split_huge_page() does not have a way to identify zero filled pages within the THP. Thus these zero pages get remapped and continue to create memory waste. In this patch, we identify and free tail pages that are zero filled in split_huge_page(). In this way, we avoid mapping these pages back into page table entries and can free up unused memory within THPs. This is based off the previously mentioned patchset by Yu Zhao. However, we chose to free zero tail pages whenever they are encountered instead of only on reclaim or migration. We also add a self test to verify the RssAnon value to make sure zero pages are not remapped.
--- include/linux/rmap.h | 2 +- include/linux/vm_event_item.h | 2 + mm/huge_memory.c | 43 +++++++++++-- mm/migrate.c | 60 ++++++++++++++++--- mm/migrate_device.c | 4 +- mm/vmstat.c | 2 + .../selftests/vm/split_huge_page_test.c | 58 +++++++++++++++++- tools/testing/selftests/vm/vm_util.c | 23 +++++++ tools/testing/selftests/vm/vm_util.h | 1 + 9 files changed, 180 insertions(+), 15 deletions(-)
include/linux/rmap.h +1 -1
@@ -369,7 +369,7 @@ int folio_mkclean(struct folio *);int pfn_mkclean_range (unsigned long pfn,unsigned long nr_pages,pgoff_t pgoff,struct vm_area_struct *vma); -void remove_migration_ptes (struct folio *src,struct folio *dst,bool locked); +void remove_migration_ptes (struct folio *src,struct folio *dst,bool locked,bool unmap_clean);int page_mapped_in_vma (struct page *page,struct vm_area_struct *vma);
include/linux/vm_event_item.h +2 -0
@@ -104,6 +104,8 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT,#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD THP_SPLIT_PUD , #endif +THP_SPLIT_FREE , +THP_SPLIT_UNMAP ,THP_ZERO_PAGE_ALLOC ,THP_ZERO_PAGE_ALLOC_FAILED , THP_SWPOUT,
mm/huge_memory.c +39 -4
85 lines not on this page · raw message
mm/migrate.c +53 -7
106 lines not on this page · raw message
mm/migrate_device.c +2 -2
@@ -407,7 +407,7 @@ static void migrate_vma_unmap(struct migrate_vma *migrate)continue ; folio =page_folio (page); -remove_migration_ptes (folio, folio, false); +remove_migration_ptes (folio, folio, false, false); migrate->src[i] =0 ;folio_unlock (folio);
@@ -783,7 +783,7 @@ void migrate_vma_finalize(struct migrate_vma *migrate) src =page_folio (page); dst =page_folio (newpage); -remove_migration_ptes (src, dst, false); +remove_migration_ptes (src, dst, false, false);folio_unlock (src);if (is_zone_device_page (page))
mm/vmstat.c +2 -0
@@ -1363,6 +1363,8 @@ const char * const vmstat_text[] = {#ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD "thp_split_pud" , #endif +"thp_split_free" , +"thp_split_unmap" ,"thp_zero_page_alloc" ,"thp_zero_page_alloc_failed" , "thp_swpout",
tools/testing/selftests/vm/split_huge_page_test.c +57 -1
76 lines not on this page · raw message
tools/testing/selftests/vm/vm_util.c +23 -0
@@ -6,6 +6,7 @@#define PMD_SIZE_FILE_PATH "/sys/kernel/mm/transparent_hugepage/hpage_pmd_size"#define SMAP_FILE_PATH "/proc/self/smaps" +#define STATUS_FILE_PATH "/proc/self/status"#define MAX_LINE_LENGTH 500uint64_t pagemap_get_entry (int fd,char *start)
@@ -72,6 +73,28 @@ uint64_t read_pmd_pagesize(void)return strtoul (buf,NULL ,10 ); } +uint64_t rss_anon (void ) +{ +uint64_t rss_anon =0 ; +int ret; +FILE *fp; +char buffer[MAX_LINE_LENGTH ]; + + fp =fopen (STATUS_FILE_PATH ,"r" ); +if (!fp) +ksft_exit_fail_msg ("%s: Failed to open file %s\n" , __func__,STATUS_FILE_PATH ); + +if (!check_for_pattern (fp,"RssAnon:" , buffer)) + goto err_out; + +if (sscanf (buffer,"RssAnon:%10ld kB" , &rss_anon) !=1 ) +ksft_exit_fail_msg ("Reading status error\n" ); + +err_out: +fclose (fp); +return rss_anon; +} +uint64_t check_huge (void *addr) {uint64_t thp =0 ;
tools/testing/selftests/vm/vm_util.h +1 -0
@@ -6,4 +6,5 @@ uint64_t pagemap_get_entry(int fd, char *start);bool pagemap_is_softdirty (int fd,char *start);void clear_softdirty (void );uint64_t read_pmd_pagesize (void ); +uint64_t rss_anon (void );uint64_t check_huge (void *addr);
Signature · 1 line
2.30.2
Re: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
Quoted 10 lines · On 25.08.22 23:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
Currently, when /sys/kernel/mm/transparent_hugepage/enabled=always is set there are a large number of transparent hugepages that are almost entirely zero filled. This is mentioned in a number of previous patchsets including:
https://lore.kernel.org/all/[email protected]/
https://lore.kernel.org/all/
[email protected]/
Quoted 10 lines
Currently,
split_huge_page()does not have a way to identify zero filled pages within the THP. Thus these zero pages get remapped and continue to create memory waste. In this patch, we identify and free tail pages that are zero filled insplit_huge_page(). In this way, we avoid mapping these pages back into page table entries and can free up unused memory within THPs. This is based off the previously mentioned patchset by Yu Zhao. However, we chose to free zero tail pages whenever they are encountered instead of only on reclaim or migration. We also add a self test to verify the RssAnon value to make sure zero pages are not remapped.
Isn't this to some degree splitting the THP (PMDs->PTEs + dissolve compound page) and then letting KSM replace the zero-filled page by the shared zeropage?
Signature · 3 lines
Thanks, David / dhildenb
Re: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
Quoted 31 lines · David Hildenbrand
!-------------------------------------------------------------------|
This Message Is From an External Sender|-------------------------------------------------------------------!
Quoted 10 lines · On 25.08.22 23:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
Currently, when /sys/kernel/mm/transparent_hugepage/enabled=always is set there are a large number of transparent hugepages that are almost entirely zero filled. This is mentioned in a number of previous patchsets including:
https://lore.kernel.org/all/[email protected]/
https://lore.kernel.org/all/
[email protected]/Quoted 10 lines
Currently,
split_huge_page()does not have a way to identify zero filled pages within the THP. Thus these zero pages get remapped and continue to create memory waste. In this patch, we identify and free tail pages that are zero filled insplit_huge_page(). In this way, we avoid mapping these pages back into page table entries and can free up unused memory within THPs. This is based off the previously mentioned patchset by Yu Zhao. However, we chose to free zero tail pages whenever they are encountered instead of only on reclaim or migration. We also add a self test to verify the RssAnon value to make sure zero pages are not remapped.
Quoted 8 lines
Isn't this to some degree splitting the THP (
PMDs->PTEs+ dissolve compound page) and then letting KSM replace the zero-filled page by the shared zeropage?Signature · 3 lines
Thanks, David / dhildenb
AFAICT KSM may or may not replace the zero filled page with the shared zero page depending on whether the VMA is mergeable. Whether
or not the VMA is mergeable comes from madvise. Madvise only applies to certain memory regions. Here we have THP always enabled rather than on madvise, and the end goal is to deprecate madvise entirely.
These THPs would previously not have been split at all, as we could not identify which THPs were underutilized, and would thus have just been memory waste when THP was always enabled.
In split_huge_page() we chose the most straightforward approach to free (zap) the zero page immediately to get rid of the memory waste. It does not seem to me that KSM is necessary here.
Thanks,
Alex
Rik van Riel
reviewerRe: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
In reply to David HildenbrandQuoted 16 lines · David Hildenbrand
Quoted 14 lines · On 25.08.22 23:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
Currently,
split_huge_page()does not have a way to identify zero filled
pages within the THP. Thus these zero pages get remapped and continue to
create memory waste. In this patch, we identify and free tail pages that
are zero filled insplit_huge_page(). In this way, we avoid mapping these
pages back into page table entries and can free up unused memory within
THPs.
Quoted 5 lines
Isn't this to some degree splitting the THP (
PMDs->PTEs+ dissolve compound page) and then letting KSM replace the zero-filled page by the
shared zeropage?
Many systems do not run KSM, though, and even on the systems where it does, KSM only covers a subset of the memory in the system.
I could see wanting to maybe consolidate the scanning between KSM and this thing at some point, if it could be done without too much complexity, but keeping this change to split_huge_page looks like it might make sense even when KSM is enabled, since it will get rid of the unnecessary memory much faster than KSM could.
Keeping a hundred MB of unnecessary memory around for longer would simply result in more THPs getting split up, and more memory pressure for a longer time than we need.
Signature · 1 line
All Rights Reversed.
Zi Yan
reviewerRe: [RFC 1/3] mm: add thp_utilization metrics to debugfs
In reply to [email protected]Quoted 9 lines · On 25 Aug 2022, at 17:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
This change introduces a tool that scans through all of physical memory for anonymous THPs and groups them into buckets based on utilization. It also includes an interface under
/sys/kernel/debug/thp_utilization.Sample Output:
Quoted 12 lines
Utilized[0-50]: 1331 680884
Utilized[51-101]: 9 3983
Utilized[102-152]: 3 1187
Utilized[153-203]: 0 0
Utilized[204-255]: 2 539
Utilized[256-306]: 5 1135
Utilized[307-357]: 1 192
Utilized[358-408]: 0 0
Utilized[409-459]: 1 57
Utilized[460-512]: 400 13
Last Scan Time: 223.98
Last Scan Duration: 70.65
How large is the memory? Just wonder the scanning speed. Also, it might be better to explicitly add the time unit, second, in the output.
Quoted 146 lines
This indicates that there are 1331 THPs that have between 0 and 50 utilized (non zero) pages. In total there are 680884 zero pages in this utilization bucket. THPs in the [0-50] bucket compose 76% of total THPs, and are responsible for 99% of total zero pages across all THPs. In other words, the least utilized THPs are responsible for almost all of the memory waste when THP is always enabled. Similar results have been observed across production workloads.
The last two lines indicate the timestamp and duration of the most recent scan through all of physical memory. Here we see that the last scan occurred 223.98 seconds after boot time and took 70.65 seconds.
Utilization of a THP is defined as the percentage of nonzero pages in the THP. The worker thread will scan through all of physical memory and obtain utilization of all anonymous THPs. It will gather this information by periodically scanning through all of physical memory for anonymous THPs, group them into buckets based on utilization, and report utilization information through debugfs under
/sys/kernel/debug/thp_utilization.Signed-off-by Alexander Zhu <[email protected]>--- Documentation/admin-guide/mm/transhuge.rst | 9 + include/linux/huge_mm.h | 2 + mm/huge_memory.c | 198 +++++++++++++++++++++ 3 files changed, 209 insertions(+)Documentation/admin-guide/mm/transhuge.rst +9
-0@@ -297,6 +297,15 @@ To identify what applications are mapping file transparent huge pages, it is necessary to read ``/proc/PID/smaps`` and count the FileHugeMapped fields for each mapping. +The utilization of transparent hugepages can be viewed by reading +``/sys/kernel/debug/thp_utilization``. The utilization of a THP is defined +as the ratio of non zero filled 4kb pages to the total number of pages in a +THP. The buckets are labelled by the range of total utilized 4kb pages with +one line per utilization bucket. Each line contains the total number of +THPs in that bucket and the total number of zero filled 4kb pages summed +over all THPs in that bucket. The last two lines show the timestamp and +duration respectively of the most recent scan over all of physical memory. + Note that reading the smaps file is expensive and reading it frequently will incur overhead.include/linux/huge_mm.h +2
-0@@ -179,6 +179,8 @@ bool hugepage_vma_check(struct vm_area_struct *vma,unsigned long thp_get_unmapped_area (struct file *filp,unsigned long addr,unsigned long len,unsigned long pgoff,unsigned long flags); +int thp_number_utilized_pages (struct page *page); +void prep_transhuge_page (struct page *page);void free_transhuge_page (struct page *page);mm/huge_memory.c +49
-0@@ -45,6 +45,21 @@#define CREATE_TRACE_POINTS #include <trace/events/thp.h> +/* +* The number of utilization buckets THPs will be grouped in +* under /sys/kernel/debug/thp_utilization. +*/ +#define THP_UTIL_BUCKET_NR 10 +/* +* The maximum number of hugepages to scan through on each periodic +* run of the scanner that generates /sys/kernel/debug/thp_utilization. +* We scan through physical memory in chunks of size PMD_SIZE and +* record the timestamp and duration of each scan. In practice we have +* found that scanning THP_UTIL_SCAN_SIZE hugepages per second is sufficient +* for obtaining useful utilization metrics and does not have a noticeable +* impact on CPU. +*/ +#define THP_UTIL_SCAN_SIZE 256/* * By default, transparent hugepage support is disabled in order to avoid * risking an increased memory footprint for applications that are not @@ -70,6 +85,25 @@ static atomic_t huge_zero_refcount;struct page *huge_zero_page__read_mostly ;unsigned long huge_zero_pfn__read_mostly = ~0UL ; +static void thp_utilization_workfn (struct work_struct *work); +static DECLARE_DELAYED_WORK(thp_utilization_work, thp_utilization_workfn); + +struct thp_scan_info_bucket { +int nr_thps; +int nr_zero_pages; +}; + +struct thp_scan_info { +struct thp_scan_info_bucket buckets[THP_UTIL_BUCKET_NR ]; +struct zone *scan_zone; +struct timespec64 last_scan_duration; +struct timespec64 last_scan_time; +unsigned long pfn; +}; + +static struct thp_scan_info thp_scan_debugfs; +static struct thp_scan_info thp_scan; +bool hugepage_vma_check (struct vm_area_struct *vma,unsigned long vm_flags,bool smaps,bool in_pf)@@ -486,6 +520,7 @@ static int __init hugepage_init(void)if (err) goto err_slab; +schedule_delayed_work (&thp_utilization_work,HZ ); err =register_shrinker (&huge_zero_page_shrinker,"thp-zero" );if (err) goto err_hzp_shrinker;@@ -600,6 +635,11 @@ static inline bool is_transparent_hugepage(struct page *page) page[1 ].compound_dtor ==TRANSHUGE_PAGE_DTOR ; } +static inline bool is_anon_transparent_hugepage (struct page *page) +{ +return PageAnon (page) &&is_transparent_hugepage (page); +} +static unsigned long __thp_get_unmapped_area (struct file *filp,unsigned long addr,unsigned long len,loff_t off,unsigned long flags,unsigned long size)@@ -650,6 +690,38 @@ unsigned long thp_get_unmapped_area(struct file *filp, unsigned long addr, }EXPORT_SYMBOL_GPL (thp_get_unmapped_area); +int thp_number_utilized_pages (struct page *page) +{ +struct folio *folio; +unsigned long page_offset, value; +int thp_nr_utilized_pages =HPAGE_PMD_NR ; +int step_size =sizeof (unsigned long ); +bool is_all_zeroes; +void *kaddr; +int i;
mm/huge_memory.c unsigned long thp_get_unmapped_area(struct file *filp, unsigned long addr, · line 711
Quoted 10 lines
mm/huge_memory.c +10
-0+ +if (!page || !is_anon_transparent_hugepage (page)) +return -1 ; + + folio =page_folio (page); +for (i =0 ; i <folio_nr_pages (folio); i++) { + kaddr =kmap_local_folio (folio, i); + is_all_zeroes = true; +for (page_offset =0 ; page_offset <PAGE_SIZE ; page_offset += step_size) { + value = *(unsigned long *)(kaddr + page_offset);
Is it possible to use cache-bypassing read to avoid cache pollution? You are scanning for 256*2M at a time. Wouldn’t that wipe out all the useful data in the cache?
Quoted 155 lines
mm/huge_memory.c +139
-0+if (value !=0 ) { + is_all_zeroes = false; +break ; + } + } +if (is_all_zeroes) + thp_nr_utilized_pages--; + +kunmap_local (kaddr); + } +return thp_nr_utilized_pages; +} +static vm_fault_t __do_huge_pmd_anonymous_page (struct vm_fault *vmf,struct page *page,gfp_t gfp) {@@ -3135,6 +3207,42 @@ static int __init split_huge_pages_debugfs(void)return 0 ; }late_initcall (split_huge_pages_debugfs); + +static int thp_utilization_show (struct seq_file *seqf,void *pos) +{ +int i; +int start; +int end; + +for (i =0 ; i <THP_UTIL_BUCKET_NR ; i++) { + start = i *HPAGE_PMD_NR /THP_UTIL_BUCKET_NR ; + end = (i +1 ==THP_UTIL_BUCKET_NR ) + ?HPAGE_PMD_NR + : ((i +1 ) *HPAGE_PMD_NR /THP_UTIL_BUCKET_NR -1 ); +/* The last bucket will need to contain 100 */ +seq_printf (seqf,"Utilized[%d-%d]: %d %d\n" , start, end, + thp_scan_debugfs.buckets[i].nr_thps, + thp_scan_debugfs.buckets[i].nr_zero_pages); + } +seq_printf (seqf,"Last Scan Time: %lu.%02lu\n" , + (unsigned long )thp_scan_debugfs.last_scan_time.tv_sec, + (thp_scan_debugfs.last_scan_time.tv_nsec / (NSEC_PER_SEC /100 ))); + +seq_printf (seqf,"Last Scan Duration: %lu.%02lu\n" , + (unsigned long )thp_scan_debugfs.last_scan_duration.tv_sec, + (thp_scan_debugfs.last_scan_duration.tv_nsec / (NSEC_PER_SEC /100 ))); + +return 0 ; +} +DEFINE_SHOW_ATTRIBUTE (thp_utilization); + +static int __init thp_utilization_debugfs (void ) +{ +debugfs_create_file ("thp_utilization" ,0200 ,NULL ,NULL , + &thp_utilization_fops); +return 0 ; +} +late_initcall (thp_utilization_debugfs);#endif #ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION @@ -3220,3 +3328,93 @@ void remove_migration_pmd(struct page_vma_mapped_walk *pvmw, struct page *new)trace_remove_migration_pmd (address,pmd_val (pmde)); }#endif + +static void thp_scan_next_zone (void ) +{ +struct timespec64 current_time; +int i; +bool update_debugfs; +/* +* THP utilization worker thread has reached the end +* of the memory zone. Proceed to the next zone. +*/ + thp_scan.scan_zone =next_zone (thp_scan.scan_zone); + update_debugfs = !thp_scan.scan_zone; + thp_scan.scan_zone = update_debugfs ? (first_online_pgdat ())->node_zones + : thp_scan.scan_zone; + thp_scan.pfn = (thp_scan.scan_zone->zone_start_pfn +HPAGE_PMD_NR -1 ) + & ~(HPAGE_PMD_SIZE -1 ); +if (!update_debugfs) +return ; +/* +* If the worker has scanned through all of physical +* memory. Then update information displayed in /sys/kernel/debug/thp_utilization +*/ +ktime_get_ts64 (¤t_time); + thp_scan_debugfs.last_scan_duration =timespec64_sub (current_time, + thp_scan_debugfs.last_scan_time); + thp_scan_debugfs.last_scan_time = current_time; + +for (i =0 ; i <THP_UTIL_BUCKET_NR ; i++) { + thp_scan_debugfs.buckets[i].nr_thps = thp_scan.buckets[i].nr_thps; + thp_scan_debugfs.buckets[i].nr_zero_pages = thp_scan.buckets[i].nr_zero_pages; + thp_scan.buckets[i].nr_thps =0 ; + thp_scan.buckets[i].nr_zero_pages =0 ; + } +} + +static void thp_util_scan (unsigned long pfn_end) +{ +struct page *page =NULL ; +int bucket, num_utilized_pages, current_pfn; +int i; +/* +* Scan through each memory zone in chunks of up to THP_UTIL_SCAN_SIZE +* hugepages every second looking for anonymous THPs. +*/ +for (i =0 ; i <THP_UTIL_SCAN_SIZE ; i++) { + current_pfn = thp_scan.pfn; + thp_scan.pfn +=HPAGE_PMD_NR ; +if (current_pfn >= pfn_end) +return ; + +if (!pfn_valid (current_pfn)) +continue ; + + page =pfn_to_page (current_pfn); + num_utilized_pages =thp_number_utilized_pages (page); +/* Not a THP; skip it. */ +if (num_utilized_pages <0 ) +continue ; +/* Group THPs into utilization buckets */ + bucket = num_utilized_pages *THP_UTIL_BUCKET_NR /HPAGE_PMD_NR ; + bucket =min (bucket,THP_UTIL_BUCKET_NR -1 ); + thp_scan.buckets[bucket].nr_thps++; + thp_scan.buckets[bucket].nr_zero_pages += (HPAGE_PMD_NR - num_utilized_pages); + } +} + +static void thp_utilization_workfn (struct work_struct *work) +{ +unsigned long pfn_end; + +if (!thp_scan.scan_zone) + thp_scan.scan_zone = (first_online_pgdat ())->node_zones; +/* +* Worker function that scans through all of physical memory +* for anonymous THPs. +*/ + pfn_end = (thp_scan.scan_zone->zone_start_pfn + + thp_scan.scan_zone->spanned_pages +HPAGE_PMD_NR -1 ) + & ~(HPAGE_PMD_SIZE -1 ); +/* If we have reached the end of the zone or end of physical memory +* move on to the next zone. Otherwise, scan the next PFNs in the +* current zone. +*/ +if (!populated_zone (thp_scan.scan_zone) || thp_scan.pfn >= pfn_end) +thp_scan_next_zone (); +else +thp_util_scan (pfn_end); + +schedule_delayed_work (&thp_utilization_work,HZ ); +}Signature · 1 line
2.30.2
--
Best Regards,
Yan, Zi
Zi Yan
reviewerRe: [RFC 3/3] mm: THP low utilization shrinker
In reply to [email protected]Quoted 11 lines · On 25 Aug 2022, at 17:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
This patch introduces a shrinker that will remove THPs in the lowest utilization bucket. As previously mentioned, we have observed that almost all of the memory waste when THPs are always enabled is contained in the lowest utilization bucket. The shrinker will add these THPs to a
list_lruand split anonymous THPs based off information from kswapd. It requires the changes fromthp_utilizationto identify the least utilized THPs, and the changes tosplit_huge_pageto identify and free zero pages within THPs.
How stale could the information in the utilization bucket be? Is it possible that THP shrinker splits a THP used to have a lot of zero-filled subpages but now have all subpages filled with useful values? In Patch 2, split_huge_page() only unmap zero-filled subpages, but for THP shrinker, should it verify the utilization before it splits the page?
Quoted 378 lines
Signed-off-by Alexander Zhu <[email protected]>--- include/linux/huge_mm.h | 7 +++ include/linux/list_lru.h | 24 +++++++++++ include/linux/mm_types.h | 5 +++ mm/huge_memory.c | 92 ++++++++++++++++++++++++++++++++++++++-- mm/list_lru.c | 49 +++++++++++++++++++++ mm/page_alloc.c | 6 +++ 6 files changed, 180 insertions(+), 3 deletions(-)include/linux/huge_mm.h +7
-0@@ -192,6 +192,8 @@ static inline int split_huge_page(struct page *page) }void deferred_split_huge_page (struct page *page); +void add_underutilized_thp (struct page *page); +void __split_huge_pmd (struct vm_area_struct *vma,pmd_t *pmd,unsigned long address,bool freeze,struct folio *folio);@@ -302,6 +304,11 @@ static inline struct list_head *page_deferred_list(struct page *page) return &page[2].deferred_list; } +static inline struct list_head *page_underutilized_thp_list (struct page *page) +{ +return &page[3 ].underutilized_thp_list; +} +#else /* CONFIG_TRANSPARENT_HUGEPAGE */ #define HPAGE_PMD_SHIFT ({ BUILD_BUG(); 0; })#define HPAGE_PMD_MASK ({ BUILD_BUG(); 0; })include/linux/list_lru.h +24
-0@@ -89,6 +89,18 @@ void memcg_reparent_list_lrus(struct mem_cgroup *memcg, struct mem_cgroup *paren*/ bool list_lru_add (struct list_lru *lru,struct list_head *item); +/** +* list_lru_add_page: add an element to the lru list's tail +* @list_lru: the lru pointer +* @page: the page containing the item +* @item: the item to be deleted. +* +* This function works the same as list_lru_add in terms of list +* manipulation. Used for non slab objects contained in the page. +* +* Return value: true if the list was updated, false otherwise +*/ +bool list_lru_add_page (struct list_lru *lru,struct page *page,struct list_head *item);/** * list_lru_del: delete an element to the lru list * @list_lru: the lru pointer @@ -102,6 +114,18 @@ bool list_lru_add(struct list_lru *lru, struct list_head *item);*/ bool list_lru_del (struct list_lru *lru,struct list_head *item); +/** +* list_lru_del_page: delete an element to the lru list +* @list_lru: the lru pointer +* @page: the page containing the item +* @item: the item to be deleted. +* +* This function works the same as list_lru_del in terms of list +* manipulation. Used for non slab objects contained in the page. +* +* Return value: true if the list was updated, false otherwise +*/ +bool list_lru_del_page (struct list_lru *lru,struct page *page,struct list_head *item);/** * list_lru_count_one: return the number of objects currently held by @lru * @lru: the lru pointer. include/linux/mm_types.h +5
-0@@ -151,6 +151,11 @@ struct page {/* For both global and memcg */ struct list_head deferred_list; }; +struct {/* Third tail page of compound page */ +unsigned long _compound_pad_3;/* compound_head */ +unsigned long _compound_pad_4; +struct list_head underutilized_thp_list; + };struct {/* Page table pages */ unsigned long _pt_pad_1;/* compound_head */ pgtable_t pmd_huge_pte;/* protected by page->ptl */ mm/huge_memory.c +89
-3@@ -8,6 +8,7 @@#include <linux/mm.h> #include <linux/sched.h> #include <linux/sched/mm.h> +#include <linux/sched/clock.h> #include <linux/sched/coredump.h> #include <linux/sched/numa_balancing.h> #include <linux/highmem.h> @@ -85,6 +86,8 @@ static atomic_t huge_zero_refcount;struct page *huge_zero_page__read_mostly ;unsigned long huge_zero_pfn__read_mostly = ~0UL ; +struct list_lru huge_low_util_page_lru; +static void thp_utilization_workfn (struct work_struct *work); static DECLARE_DELAYED_WORK(thp_utilization_work, thp_utilization_workfn);@@ -269,6 +272,46 @@ static struct shrinker huge_zero_page_shrinker = { .seeks = DEFAULT_SEEKS, }; +static enum lru_status low_util_free_page (struct list_head *item, +struct list_lru_one *lru, +spinlock_t *lock, +void *cb_arg) +{ +struct page *head =compound_head (list_entry (item, + struct page, + underutilized_thp_list)); + +if (get_page_unless_zero (head)) { +lock_page (head); +list_lru_isolate (lru, item); +split_huge_page (head); +unlock_page (head); +put_page (head); + } + +return LRU_REMOVED_RETRY ; +} + +static unsigned long shrink_huge_low_util_page_count (struct shrinker *shrink, +struct shrink_control *sc) +{ +return list_lru_shrink_count (&huge_low_util_page_lru, sc); +} + +static unsigned long shrink_huge_low_util_page_scan (struct shrinker *shrink, +struct shrink_control *sc) +{ +return list_lru_shrink_walk (&huge_low_util_page_lru, sc, low_util_free_page,NULL ); +} + +static struct shrinker huge_low_util_page_shrinker = { + .count_objects = shrink_huge_low_util_page_count, + .scan_objects = shrink_huge_low_util_page_scan, + .seeks =DEFAULT_SEEKS , + .flags =SHRINKER_NUMA_AWARE |SHRINKER_MEMCG_AWARE | +SHRINKER_NONSLAB , +}; +#ifdef CONFIG_SYSFS static ssize_t enabled_show (struct kobject *kobj,struct kobj_attribute *attr,char *buf)@@ -521,13 +564,18 @@ static int __init hugepage_init(void) goto err_slab;schedule_delayed_work (&thp_utilization_work,HZ ); + err =register_shrinker (&huge_low_util_page_shrinker,"thp-low-util" ); +if (err) + goto err_low_util_shrinker; err =register_shrinker (&huge_zero_page_shrinker,"thp-zero" );if (err) goto err_hzp_shrinker; err =register_shrinker (&deferred_split_shrinker,"thp-deferred_split" );if (err) goto err_split_shrinker; - + err =list_lru_init_memcg (&huge_low_util_page_lru, &huge_low_util_page_shrinker); +if (err) + goto err_low_util_list_lru;/* * By default disable transparent hugepages on smaller systems, * where the extra memory used could hurt more than TLB overhead @@ -543,11 +591,16 @@ static int __init hugepage_init(void) goto err_khugepaged;return 0 ; + err_khugepaged: +list_lru_destroy (&huge_low_util_page_lru); +err_low_util_list_lru:unregister_shrinker (&deferred_split_shrinker); err_split_shrinker:unregister_shrinker (&huge_zero_page_shrinker); err_hzp_shrinker: +unregister_shrinker (&huge_low_util_page_shrinker); +err_low_util_shrinker:khugepaged_destroy (); err_slab:hugepage_exit_sysfs (hugepage_kobj);@@ -622,6 +675,7 @@ void prep_transhuge_page(struct page *page)*/ INIT_LIST_HEAD (page_deferred_list (page)); +INIT_LIST_HEAD (page_underutilized_thp_list (page));set_compound_page_dtor (page,TRANSHUGE_PAGE_DTOR ); }@@ -2491,8 +2545,7 @@ static void __split_huge_page_tail(struct page *head, int tail, (1L << PG_dirty)));/* ->mapping in first tail page is compound_mapcount */ -VM_BUG_ON_PAGE (tail >2 && page_tail->mapping !=TAIL_MAPPING , - page_tail); +VM_BUG_ON_PAGE (tail >3 && page_tail->mapping !=TAIL_MAPPING , page_tail); page_tail->mapping = head->mapping; page_tail->index = head->index + tail; page_tail->private =0 ;@@ -2698,6 +2751,7 @@ int split_huge_page_to_list(struct page *page, struct list_head *list)struct folio *folio =page_folio (page);struct page *head = &folio->page;struct deferred_split *ds_queue =get_deferred_split_queue (head); +struct list_head *underutilized_thp_list =page_underutilized_thp_list (head);XA_STATE (xas, &head->mapping->i_pages, head->index);struct anon_vma *anon_vma =NULL ;struct address_space *mapping =NULL ;@@ -2796,6 +2850,8 @@ int split_huge_page_to_list(struct page *page, struct list_head *list)list_del (page_deferred_list (head)); }spin_unlock (&ds_queue->split_queue_lock); +if (!list_empty (underutilized_thp_list)) +list_lru_del_page (&huge_low_util_page_lru, head, underutilized_thp_list);if (mapping) {int nr =thp_nr_pages (head);@@ -2838,6 +2894,7 @@ int split_huge_page_to_list(struct page *page, struct list_head *list)void free_transhuge_page (struct page *page) {struct deferred_split *ds_queue =get_deferred_split_queue (page); +struct list_head *underutilized_thp_list =page_underutilized_thp_list (page);unsigned long flags;spin_lock_irqsave (&ds_queue->split_queue_lock, flags);@@ -2846,6 +2903,12 @@ void free_transhuge_page(struct page *page)list_del (page_deferred_list (page)); }spin_unlock_irqrestore (&ds_queue->split_queue_lock, flags); +if (!list_empty (underutilized_thp_list)) +list_lru_del_page (&huge_low_util_page_lru, page, underutilized_thp_list); + +if (PageLRU (page)) +__clear_page_lru_flags (page); +free_compound_page (page); }@@ -2886,6 +2949,26 @@ void deferred_split_huge_page(struct page *page)spin_unlock_irqrestore (&ds_queue->split_queue_lock, flags); } +void add_underutilized_thp (struct page *page) +{ +VM_BUG_ON_PAGE (!PageTransHuge (page), page); + +if (PageSwapCache (page)) +return ; + +/* +* Need to take a reference on the page to prevent the page from getting free'd from +* under us while we are adding the THP to the shrinker. +*/ +if (!get_page_unless_zero (page)) +return ; + +if (!is_huge_zero_page (page) &&is_anon_transparent_hugepage (page)) +list_lru_add_page (&huge_low_util_page_lru, page,page_underutilized_thp_list (page)); + +put_page (page); +} +static unsigned long deferred_split_count (struct shrinker *shrink,struct shrink_control *sc) {@@ -3424,6 +3507,9 @@ static void thp_util_scan(unsigned long pfn_end)/* Group THPs into utilization buckets */ bucket = num_utilized_pages *THP_UTIL_BUCKET_NR /HPAGE_PMD_NR ; bucket =min (bucket,THP_UTIL_BUCKET_NR -1 ); +if (bucket ==0 ) +add_underutilized_thp (page); + thp_scan.buckets[bucket].nr_thps++; thp_scan.buckets[bucket].nr_zero_pages += (HPAGE_PMD_NR - num_utilized_pages); }mm/list_lru.c +49
-0@@ -140,6 +140,32 @@ bool list_lru_add(struct list_lru *lru, struct list_head *item) }EXPORT_SYMBOL_GPL (list_lru_add); +bool list_lru_add_page (struct list_lru *lru,struct page *page,struct list_head *item) +{ +int nid =page_to_nid (page); +struct list_lru_node *nlru = &lru->node[nid]; +struct list_lru_one *l; +struct mem_cgroup *memcg; + +spin_lock (&nlru->lock); +if (list_empty (item)) { + memcg =page_memcg (page); +memcg_list_lru_alloc (memcg, lru,GFP_KERNEL ); + l =list_lru_from_memcg_idx (lru, nid,memcg_kmem_id (memcg)); +list_add_tail (item, &l->list); +/* Set shrinker bit if the first element was added */ +if (!l->nr_items++) +set_shrinker_bit (memcg, nid, +lru_shrinker_id (lru)); + nlru->nr_items++; +spin_unlock (&nlru->lock); +return true; + } +spin_unlock (&nlru->lock); +return false; +} +EXPORT_SYMBOL_GPL (list_lru_add_page); +bool list_lru_del (struct list_lru *lru,struct list_head *item) {int nid =page_to_nid (virt_to_page (item));@@ -160,6 +186,29 @@ bool list_lru_del(struct list_lru *lru, struct list_head *item) }EXPORT_SYMBOL_GPL (list_lru_del); +bool list_lru_del_page (struct list_lru *lru,struct page *page,struct list_head *item) +{ +int nid =page_to_nid (page); +struct list_lru_node *nlru = &lru->node[nid]; +struct list_lru_one *l; +struct mem_cgroup *memcg; + +spin_lock (&nlru->lock); +if (!list_empty (item)) { + memcg =page_memcg (page); +memcg_list_lru_alloc (memcg, lru,GFP_KERNEL ); + l =list_lru_from_memcg_idx (lru, nid,memcg_kmem_id (memcg)); +list_del_init (item); + l->nr_items--; + nlru->nr_items--; +spin_unlock (&nlru->lock); +return true; + } +spin_unlock (&nlru->lock); +return false; +} +EXPORT_SYMBOL_GPL (list_lru_del_page); +void list_lru_isolate (struct list_lru_one *list,struct list_head *item) {list_del_init (item);mm/page_alloc.c +6
-0@@ -1327,6 +1327,12 @@ static int free_tail_pages_check(struct page *head_page, struct page *page)* deferred_list.next -- ignore value. */ break ; +case 3 : +/* +* the third tail page: ->mapping is +* underutilized_thp_list.next -- ignore value. +*/ +break ;default :if (page->mapping !=TAIL_MAPPING ) {bad_page (page,"corrupted mapping in tail page" );Signature · 1 line
2.30.2
--
Best Regards,
Yan, Zi
Re: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
In reply to Rik van RielQuoted 17 lines · Rik van Riel
Quoted 16 lines · David Hildenbrand
Quoted 14 lines · On 25.08.22 23:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
Currently,
split_huge_page()does not have a way to identify zero filled
pages within the THP. Thus these zero pages get remapped and continue to
create memory waste. In this patch, we identify and free tail pages that
are zero filled insplit_huge_page(). In this way, we avoid mapping these
pages back into page table entries and can free up unused memory within
THPs.
Quoted 8 lines
Quoted 5 lines
Isn't this to some degree splitting the THP (
PMDs->PTEs+ dissolve compound page) and then letting KSM replace the zero-filled page by the
shared zeropage?Many systems do not run KSM, though, and even on the systems where it does, KSM only covers a subset of the memory in the system.
Right, however there seems to be a push from some folks to enable it more widely.
Quoted 10 lines
I could see wanting to maybe consolidate the scanning between KSM and this thing at some point, if it could be done without too much complexity, but keeping this change to
split_huge_pagelooks like it might make sense even when KSM is enabled, since it will get rid of the unnecessary memory much faster than KSM could.Keeping a hundred MB of unnecessary memory around for longer would simply result in more THPs getting split up, and more memory pressure for a longer time than we need.
Right. I was wondering if we want to map the shared zeropage instead of the "detected to be zero" page, similar to how KSM would do it. For example, with userfaultfd there would be an observable difference.
(maybe that's already done in this patch set)
Signature · 3 lines
Thanks, David / dhildenb
Rik van Riel
reviewerRe: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
Quoted 16 lines · David Hildenbrand
Quoted 11 lines · Rik van Riel
Quoted 2 lines · David Hildenbrand
Quoted 1 line · On 25.08.22 23:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
I could see wanting to maybe consolidate the scanning between KSM and this thing at some point, if it could be done without too much complexity, but keeping this change to
split_huge_pagelooks like it might make sense even when KSM is enabled, since it will get rid of the unnecessary memory much faster than KSM could.Quoted 3 lines
Keeping a hundred MB of unnecessary memory around for longer would simply result in more THPs getting split up, and more memory pressure for a longer time than we need.
Quoted 7 lines
Right. I was wondering if we want to map the shared zeropage instead of
the "detected to be zero" page, similar to how KSM would do it. For example, with userfaultfd there would be an observable difference.(maybe that's already done in this patch set)
The patch does not currently do that, but I suppose it could?
What exactly are the userfaultfd differences here, and how does dropping 4kB pages break things vs. using the shared zeropage?
Signature · 1 line
All Rights Reversed.
Re: [RFC 1/3] mm: add thp_utilization metrics to debugfs
In reply to Zi YanQuoted 9 lines · Zi Yan
On 25 Aug 2022, at 17:30, [email protected] wrote:
How large is the memory? Just wonder the scanning speed. Also, it might be better to explicitly add the time unit, second, in the output.
The size of memory was 65GB on the test machine I obtained these numbers on. I’ll take note of adding the time unit. Thanks!
Quoted 3 lines
Is it possible to use cache-bypassing read to avoid cache pollution? You are scanning for 256*2M at a time. Wouldn’t that wipe out all the useful data in the cache?
I have only found non-temporal writes in arch/x86/, not non-temporal reads (with MOVNTDQA). I suppose we should figure out why nobody ever bothered using non-temporal reads on x86 before trying to make this code use them.
A quick search of the internet suggests they non-temporal reads are not being used on x86 because people could not show a performance improvement by using them, but maybe somebody here has more insight?
Re: [RFC 3/3] mm: THP low utilization shrinker
In reply to Zi YanQuoted 2 lines
How stale could the information in the utilization bucket be?
The staleness would be capped by the duration of the scan, 70s in in the example.
Quoted 3 lines
Is it possible that THP shrinker splits a THP used to have a lot of zero-filled subpages but now have all subpages filled with useful values?
This is possible, but we free only the zero pages, which cannot have any useful values. How often it happens that THPs move utilization buckets should be workload dependent.
Quoted 3 lines
In Patch 2,
split_huge_page()only unmap zero-filled subpages, but for THP shrinker, should it verify the utilization before it splits the page?
I think we should add this check to verify that it is still in the lowest bucket before having the shrinker split the page. The utilization could have changed and this way we do not need to worry about workloads where THPs move utilization buckets. Thanks!
Re: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
In reply to Rik van RielQuoted 17 lines · Rik van Riel
Quoted 16 lines · David Hildenbrand
Quoted 14 lines · Rik van Riel
Quoted 2 lines · David Hildenbrand
Quoted 1 line · On 25.08.22 23:30, [email protected] wrote:
From: Alexander Zhu <[email protected]>
I could see wanting to maybe consolidate the scanning between KSM and this thing at some point, if it could be done without too much complexity, but keeping this change to
split_huge_pagelooks like it might make sense even when KSM is enabled, since it will get rid of the unnecessary memory much faster than KSM could.Keeping a hundred MB of unnecessary memory around for longer would simply result in more THPs getting split up, and more memory pressure for a longer time than we need.
Quoted 9 lines
Quoted 7 lines
Right. I was wondering if we want to map the shared zeropage instead of
the "detected to be zero" page, similar to how KSM would do it. For example, with userfaultfd there would be an observable difference.(maybe that's already done in this patch set)
The patch does not currently do that, but I suppose it could?
It would be interesting to know why KSM decided to replace the mapped page with the shared zeropage instead of dropping the page and letting the next read fault populate the shared zeropage. That code predates userfaultfd IIRC.
Quoted 2 lines
What exactly are the userfaultfd differences here, and how does dropping 4kB pages break things vs. using the shared zeropage?
Once userfaultfd (missing mode) is enabled on a VMA:
1) khugepaged will no longer collapse pte_none(pteval), independent of khugepaged_max_ptes_none setting -- see __collapse_huge_page_isolate. [it will also not collapse zeropages, but I recall that that's not actually required]
So it will not close holes, because the user space fault handler is in charge of making a decision when something will get mapped there and with which content.
2) Page faults will no longer populate a THP -- the user space handler is notified instead and has to decide how the fault will be resolved (place pages).
If you unmap something (resulting in pte_none()) where previously something used to be mapped in a page table, you might suddenly inform the user space fault handler about a page fault that it doesn't expect, because it previously placed a page and did not zap that page itself (MADV_DONTNEED).
So at least with userfaultfd I think we have to be careful. Not sure if there are other corner cases (again, KSM behavior is interesting)
Signature · 3 lines
Thanks, David / dhildenb
Re: [RFC 2/3] mm: changes to split_huge_page() to free zero filled tail pages
Quoted 14 lines
If you unmap something (resulting in
pte_none()) where previously something used to be mapped in a page table, you might suddenly inform the user space fault handler about a page fault that it doesn't expect, because it previously placed a page and did not zap that page itself (MADV_DONTNEED).So at least with userfaultfd I think we have to be careful. Not sure if there are other corner cases (again, KSM behavior is interesting)
Signature · 3 lines
Thanks, David / dhildenb
We can implement it such that if userfaultfd is enabled on a VMA then instead of unmapping the zero page, we will map to a read only zero page.
The original patch from Yu Zhao frees zero pages only on reclaim, I am not sure it needs to be this restricted though. In use cases where immediately freeing zero pages does not work we can dedupe similar to how KSM does it.