]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mm/shmem, swap: avoid redundant Xarray lookup during swapin
authorKairui Song <kasong@tencent.com>
Mon, 28 Jul 2025 07:53:00 +0000 (15:53 +0800)
committerAndrew Morton <akpm@linux-foundation.org>
Sat, 2 Aug 2025 19:06:12 +0000 (12:06 -0700)
Patch series "mm/shmem, swap: bugfix and improvement of mTHP swap in", v6.

The current THP swapin path have several problems.  It may potentially
hang, may cause redundant faults due to false positive swap cache lookup,
and it issues redundant Xarray walks.  !CONFIG_TRANSPARENT_HUGEPAGE builds
may also contain unnecessary THP checks.

This series fixes all of the mentioned issues, the code should be more
robust and prepared for the swap table series.  Now 4 walks is reduced to
3 (get order & confirm, confirm, insert folio),
!CONFIG_TRANSPARENT_HUGEPAGE build overhead is also minimized, and comes
with a sanity check now.

The performance is slightly better after this series, sequential swap in
of 24G data from ZRAM, using transparent_hugepage_tmpfs=always (24 samples
each):

Before:         avg: 10.66s, stddev: 0.04
After patch 1:  avg: 10.58s, stddev: 0.04
After patch 2:  avg: 10.65s, stddev: 0.05
After patch 3:  avg: 10.65s, stddev: 0.04
After patch 4:  avg: 10.67s, stddev: 0.04
After patch 5:  avg: 9.79s,  stddev: 0.04
After patch 6:  avg: 9.79s,  stddev: 0.05
After patch 7:  avg: 9.78s,  stddev: 0.05
After patch 8:  avg: 9.79s,  stddev: 0.04

Several patches improve the performance by a little, which is about ~8%
faster in total.

Build kernel test showed very slightly improvement, testing with make -j48
with defconfig in a 768M memcg also using ZRAM as swap, and
transparent_hugepage_tmpfs=always (6 test runs):

Before:         avg: 3334.66s, stddev: 43.76
After patch 1:  avg: 3349.77s, stddev: 18.55
After patch 2:  avg: 3325.01s, stddev: 42.96
After patch 3:  avg: 3354.58s, stddev: 14.62
After patch 4:  avg: 3336.24s, stddev: 32.15
After patch 5:  avg: 3325.13s, stddev: 22.14
After patch 6:  avg: 3285.03s, stddev: 38.95
After patch 7:  avg: 3287.32s, stddev: 26.37
After patch 8:  avg: 3295.87s, stddev: 46.24

This patch (of 7):

Currently shmem calls xa_get_order to get the swap radix entry order,
requiring a full tree walk.  This can be easily combined with the swap
entry value checking (shmem_confirm_swap) to avoid the duplicated lookup
and abort early if the entry is gone already.  Which should improve the
performance.

Link: https://lkml.kernel.org/r/20250728075306.12704-1-ryncsn@gmail.com
Link: https://lkml.kernel.org/r/20250728075306.12704-3-ryncsn@gmail.com
Signed-off-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Kemeng Shi <shikemeng@huaweicloud.com>
Reviewed-by: Dev Jain <dev.jain@arm.com>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Cc: Baoquan He <bhe@redhat.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Chris Li <chrisl@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Nhat Pham <nphamcs@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
mm/shmem.c

index 5e9ec28fab853ad59fbaeb4315aabb2b30d74d57..6e00d2ec40a91887e420875951292419ececca65 100644 (file)
@@ -512,15 +512,27 @@ static int shmem_replace_entry(struct address_space *mapping,
 
 /*
  * Sometimes, before we decide whether to proceed or to fail, we must check
- * that an entry was not already brought back from swap by a racing thread.
+ * that an entry was not already brought back or split by a racing thread.
  *
  * Checking folio is not enough: by the time a swapcache folio is locked, it
  * might be reused, and again be swapcache, using the same swap as before.
+ * Returns the swap entry's order if it still presents, else returns -1.
  */
-static bool shmem_confirm_swap(struct address_space *mapping,
-                              pgoff_t index, swp_entry_t swap)
+static int shmem_confirm_swap(struct address_space *mapping, pgoff_t index,
+                             swp_entry_t swap)
 {
-       return xa_load(&mapping->i_pages, index) == swp_to_radix_entry(swap);
+       XA_STATE(xas, &mapping->i_pages, index);
+       int ret = -1;
+       void *entry;
+
+       rcu_read_lock();
+       do {
+               entry = xas_load(&xas);
+               if (entry == swp_to_radix_entry(swap))
+                       ret = xas_get_order(&xas);
+       } while (xas_retry(&xas, entry));
+       rcu_read_unlock();
+       return ret;
 }
 
 /*
@@ -2293,16 +2305,20 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
                return -EIO;
 
        si = get_swap_device(swap);
-       if (!si) {
-               if (!shmem_confirm_swap(mapping, index, swap))
+       order = shmem_confirm_swap(mapping, index, swap);
+       if (unlikely(!si)) {
+               if (order < 0)
                        return -EEXIST;
                else
                        return -EINVAL;
        }
+       if (unlikely(order < 0)) {
+               put_swap_device(si);
+               return -EEXIST;
+       }
 
        /* Look it up and read it in.. */
        folio = swap_cache_get_folio(swap, NULL, 0);
-       order = xa_get_order(&mapping->i_pages, index);
        if (!folio) {
                int nr_pages = 1 << order;
                bool fallback_order0 = false;
@@ -2412,7 +2428,7 @@ alloced:
         */
        folio_lock(folio);
        if ((!skip_swapcache && !folio_test_swapcache(folio)) ||
-           !shmem_confirm_swap(mapping, index, swap) ||
+           shmem_confirm_swap(mapping, index, swap) < 0 ||
            folio->swap.val != swap.val) {
                error = -EEXIST;
                goto unlock;
@@ -2460,7 +2476,7 @@ alloced:
        *foliop = folio;
        return 0;
 failed:
-       if (!shmem_confirm_swap(mapping, index, swap))
+       if (shmem_confirm_swap(mapping, index, swap) < 0)
                error = -EEXIST;
        if (error == -EIO)
                shmem_set_folio_swapin_error(inode, index, folio, swap,