[PATCH v2 6/6] mm/truncate: use folio_split() for truncate operation.

Zi Yan posted 6 patches 3 weeks, 2 days ago
There is a newer version of this series
[PATCH v2 6/6] mm/truncate: use folio_split() for truncate operation.
Posted by Zi Yan 3 weeks, 2 days ago
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
Re: [PATCH v2 6/6] mm/truncate: use folio_split() for truncate operation.
Posted by kernel test robot 3 weeks, 1 day ago
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
Re: [PATCH v2 6/6] mm/truncate: use folio_split() for truncate operation.
Posted by kernel test robot 3 weeks, 1 day ago
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