Instead of splitting the large folio uniformly during truncation, use
buddy allocator like split at the start of truncation range to minimize
the number of resulting folios.
For example, to truncate a order-4 folio
[0, 1, 2, 3, 4, 5, ..., 15]
between [3, 10] (inclusive), folio_split() splits the folio to
[0,1], [2], [3], [4..7], [8..15] and [3], [4..7] can be dropped and
[8..15] is kept with zeros in [8..10].
It is possible to further do a folio_split() at 10, so more resulting
folios can be dropped. But it is left as future possible optimization
if needed.
Another possible optimization is to make folio_split() to split a folio
based on a given range, like [3..10] above. But that complicates
folio_split(), so it will investigated when necessary.
Signed-off-by: Zi Yan <ziy@nvidia.com>
---
include/linux/huge_mm.h | 12 ++++++++++++
mm/truncate.c | 5 ++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
index b94c2e8ee918..8048500e7bc2 100644
--- a/include/linux/huge_mm.h
+++ b/include/linux/huge_mm.h
@@ -339,6 +339,18 @@ int split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
unsigned int new_order);
int min_order_for_split(struct folio *folio);
int split_folio_to_list(struct folio *folio, struct list_head *list);
+int folio_split(struct folio *folio, unsigned int new_order, struct page *page,
+ struct list_head *list);
+static inline int split_folio_at(struct folio *folio, struct page *page,
+ struct list_head *list)
+{
+ int ret = min_order_for_split(folio);
+
+ if (ret < 0)
+ return ret;
+
+ return folio_split(folio, ret, page, list);
+}
static inline int split_huge_page(struct page *page)
{
struct folio *folio = page_folio(page);
diff --git a/mm/truncate.c b/mm/truncate.c
index e5151703ba04..dbd81c21b460 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -179,6 +179,7 @@ bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end)
{
loff_t pos = folio_pos(folio);
unsigned int offset, length;
+ long in_folio_offset;
if (pos < start)
offset = start - pos;
@@ -208,7 +209,9 @@ bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end)
folio_invalidate(folio, offset, length);
if (!folio_test_large(folio))
return true;
- if (split_folio(folio) == 0)
+
+ in_folio_offset = PAGE_ALIGN_DOWN(offset) / PAGE_SIZE;
+ if (split_folio_at(folio, folio_page(folio, in_folio_offset), NULL) == 0)
return true;
if (folio_test_dirty(folio))
return false;
--
2.45.2
Hi Zi, kernel test robot noticed the following build errors: [auto build test ERROR on akpm-mm/mm-everything] [also build test ERROR on next-20241101] [cannot apply to linus/master v6.12-rc5] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Zi-Yan/mm-huge_memory-add-two-new-yet-used-functions-for-folio_split/20241101-230623 base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything patch link: https://lore.kernel.org/r/20241101150357.1752726-7-ziy%40nvidia.com patch subject: [PATCH v2 6/6] mm/truncate: use folio_split() for truncate operation. config: arc-tb10x_defconfig (https://download.01.org/0day-ci/archive/20241103/202411030124.ZWzXWxPU-lkp@intel.com/config) compiler: arc-elf-gcc (GCC) 13.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241103/202411030124.ZWzXWxPU-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202411030124.ZWzXWxPU-lkp@intel.com/ All errors (new ones prefixed by >>): mm/truncate.c: In function 'truncate_inode_partial_folio': >> mm/truncate.c:214:13: error: implicit declaration of function 'split_folio_at'; did you mean 'split_folio'? [-Werror=implicit-function-declaration] 214 | if (split_folio_at(folio, folio_page(folio, in_folio_offset), NULL) == 0) | ^~~~~~~~~~~~~~ | split_folio cc1: some warnings being treated as errors vim +214 mm/truncate.c 166 167 /* 168 * Handle partial folios. The folio may be entirely within the 169 * range if a split has raced with us. If not, we zero the part of the 170 * folio that's within the [start, end] range, and then split the folio if 171 * it's large. split_page_range() will discard pages which now lie beyond 172 * i_size, and we rely on the caller to discard pages which lie within a 173 * newly created hole. 174 * 175 * Returns false if splitting failed so the caller can avoid 176 * discarding the entire folio which is stubbornly unsplit. 177 */ 178 bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end) 179 { 180 loff_t pos = folio_pos(folio); 181 unsigned int offset, length; 182 long in_folio_offset; 183 184 if (pos < start) 185 offset = start - pos; 186 else 187 offset = 0; 188 length = folio_size(folio); 189 if (pos + length <= (u64)end) 190 length = length - offset; 191 else 192 length = end + 1 - pos - offset; 193 194 folio_wait_writeback(folio); 195 if (length == folio_size(folio)) { 196 truncate_inode_folio(folio->mapping, folio); 197 return true; 198 } 199 200 /* 201 * We may be zeroing pages we're about to discard, but it avoids 202 * doing a complex calculation here, and then doing the zeroing 203 * anyway if the page split fails. 204 */ 205 if (!mapping_inaccessible(folio->mapping)) 206 folio_zero_range(folio, offset, length); 207 208 if (folio_needs_release(folio)) 209 folio_invalidate(folio, offset, length); 210 if (!folio_test_large(folio)) 211 return true; 212 213 in_folio_offset = PAGE_ALIGN_DOWN(offset) / PAGE_SIZE; > 214 if (split_folio_at(folio, folio_page(folio, in_folio_offset), NULL) == 0) 215 return true; 216 if (folio_test_dirty(folio)) 217 return false; 218 truncate_inode_folio(folio->mapping, folio); 219 return true; 220 } 221 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Hi Zi, kernel test robot noticed the following build errors: [auto build test ERROR on akpm-mm/mm-everything] [also build test ERROR on next-20241101] [cannot apply to linus/master v6.12-rc5] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Zi-Yan/mm-huge_memory-add-two-new-yet-used-functions-for-folio_split/20241101-230623 base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything patch link: https://lore.kernel.org/r/20241101150357.1752726-7-ziy%40nvidia.com patch subject: [PATCH v2 6/6] mm/truncate: use folio_split() for truncate operation. config: arm-multi_v4t_defconfig (https://download.01.org/0day-ci/archive/20241102/202411022321.XN6rYrgx-lkp@intel.com/config) compiler: clang version 20.0.0git (https://github.com/llvm/llvm-project 639a7ac648f1e50ccd2556e17d401c04f9cce625) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241102/202411022321.XN6rYrgx-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202411022321.XN6rYrgx-lkp@intel.com/ All errors (new ones prefixed by >>): In file included from mm/truncate.c:12: In file included from include/linux/backing-dev.h:16: In file included from include/linux/writeback.h:13: In file included from include/linux/blk_types.h:10: In file included from include/linux/bvec.h:10: In file included from include/linux/highmem.h:8: In file included from include/linux/cacheflush.h:5: In file included from arch/arm/include/asm/cacheflush.h:10: In file included from include/linux/mm.h:2211: include/linux/vmstat.h:518:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion] 518 | return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_" | ~~~~~~~~~~~ ^ ~~~ In file included from mm/truncate.c:24: In file included from mm/internal.h:13: include/linux/mm_inline.h:47:41: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion] 47 | __mod_lruvec_state(lruvec, NR_LRU_BASE + lru, nr_pages); | ~~~~~~~~~~~ ^ ~~~ include/linux/mm_inline.h:49:22: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum lru_list') [-Wenum-enum-conversion] 49 | NR_ZONE_LRU_BASE + lru, nr_pages); | ~~~~~~~~~~~~~~~~ ^ ~~~ >> mm/truncate.c:214:6: error: call to undeclared function 'split_folio_at'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 214 | if (split_folio_at(folio, folio_page(folio, in_folio_offset), NULL) == 0) | ^ 3 warnings and 1 error generated. vim +/split_folio_at +214 mm/truncate.c 166 167 /* 168 * Handle partial folios. The folio may be entirely within the 169 * range if a split has raced with us. If not, we zero the part of the 170 * folio that's within the [start, end] range, and then split the folio if 171 * it's large. split_page_range() will discard pages which now lie beyond 172 * i_size, and we rely on the caller to discard pages which lie within a 173 * newly created hole. 174 * 175 * Returns false if splitting failed so the caller can avoid 176 * discarding the entire folio which is stubbornly unsplit. 177 */ 178 bool truncate_inode_partial_folio(struct folio *folio, loff_t start, loff_t end) 179 { 180 loff_t pos = folio_pos(folio); 181 unsigned int offset, length; 182 long in_folio_offset; 183 184 if (pos < start) 185 offset = start - pos; 186 else 187 offset = 0; 188 length = folio_size(folio); 189 if (pos + length <= (u64)end) 190 length = length - offset; 191 else 192 length = end + 1 - pos - offset; 193 194 folio_wait_writeback(folio); 195 if (length == folio_size(folio)) { 196 truncate_inode_folio(folio->mapping, folio); 197 return true; 198 } 199 200 /* 201 * We may be zeroing pages we're about to discard, but it avoids 202 * doing a complex calculation here, and then doing the zeroing 203 * anyway if the page split fails. 204 */ 205 if (!mapping_inaccessible(folio->mapping)) 206 folio_zero_range(folio, offset, length); 207 208 if (folio_needs_release(folio)) 209 folio_invalidate(folio, offset, length); 210 if (!folio_test_large(folio)) 211 return true; 212 213 in_folio_offset = PAGE_ALIGN_DOWN(offset) / PAGE_SIZE; > 214 if (split_folio_at(folio, folio_page(folio, in_folio_offset), NULL) == 0) 215 return true; 216 if (folio_test_dirty(folio)) 217 return false; 218 truncate_inode_folio(folio->mapping, folio); 219 return true; 220 } 221 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
© 2016 - 2024 Red Hat, Inc.