]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
mm/swap: do not choose swap device according to numa node
authorBaoquan He <bhe@redhat.com>
Sat, 11 Oct 2025 08:16:23 +0000 (16:16 +0800)
committerAndrew Morton <akpm@linux-foundation.org>
Wed, 15 Oct 2025 04:28:51 +0000 (21:28 -0700)
Patch series "mm/swapfile.c: select the swap device with default priority
round robin", v4.

Currently, on system with multiple swap devices, swap allocation will
select one swap device according to priority.  The swap device with the
highest priority will be chosen to allocate firstly.

People can specify a priority from 0 to 32767 when swapon a swap device,
or the system will set it from -2 then downwards by default.  Meanwhile,
on NUMA system, the swap device with node_id will be considered first on
that NUMA node of the node_id.

In the current code, an array of plist, swap_avail_heads[nid], is used to
organize swap devices on each NUMA node.  For each NUMA node, there is a
plist organizing all swap devices.  The 'prio' value in the plist is the
negated value of the device's priority due to plist being sorted from low
to high.  The swap device owning one node_id will be promoted to the front
position on that NUMA node, then other swap devices are put in order of
their default priority.

E.g I got a system with 8 NUMA nodes, and I setup 4 zram partition as swap
devices.

Current behaviour:
their priorities will be(note that -1 is skipped):
NAME       TYPE      SIZE USED PRIO
/dev/zram0 partition  16G   0B   -2
/dev/zram1 partition  16G   0B   -3
/dev/zram2 partition  16G   0B   -4
/dev/zram3 partition  16G   0B   -5

And their positions in the 8 swap_avail_lists[nid] will be:
swap_avail_lists[0]: /* node 0's available swap device list */
zram0   -> zram1   -> zram2   -> zram3
prio:1     prio:3     prio:4     prio:5
swap_avali_lists[1]: /* node 1's available swap device list */
zram1   -> zram0   -> zram2   -> zram3
prio:1     prio:2     prio:4     prio:5
swap_avail_lists[2]: /* node 2's available swap device list */
zram2   -> zram0   -> zram1   -> zram3
prio:1     prio:2     prio:3     prio:5
swap_avail_lists[3]: /* node 3's available swap device list */
zram3   -> zram0   -> zram1   -> zram2
prio:1     prio:2     prio:3     prio:4
swap_avail_lists[4-7]: /* node 4,5,6,7's available swap device list */
zram0   -> zram1   -> zram2   -> zram3
prio:2     prio:3     prio:4     prio:5

The adjustment for swap device with node_id intended to decrease the
pressure of lock contention for one swap device by taking different swap
device on different node.  The adjustment was introduced in commit
a2468cc9bfdf ("swap: choose swap device according to numa node").
However, the adjustment is a little coarse-grained.  On the node, the swap
device sharing the node's id will always be selected firstly by node's
CPUs until exhausted, then next one.  And on other nodes where no swap
device shares its node id, swap device with priority '-2' will be selected
firstly until exhausted, then next with priority '-3'.

This is the swapon output during the process high pressure vm-scability
test is being taken.  It's clearly showing zram0 is heavily exploited
until exhausted.

===================================
[root@hp-dl385g10-03 ~]# swapon
NAME       TYPE      SIZE  USED PRIO
/dev/zram0 partition  16G 15.7G   -2
/dev/zram1 partition  16G  3.4G   -3
/dev/zram2 partition  16G  3.4G   -4
/dev/zram3 partition  16G  2.6G   -5

The node based strategy on selecting swap device is much better then the
old way one by one selecting swap device.  However it is still
unreasonable because swap devices are assumed to have similar accessing
speed if no priority is specified when swapon.  It's unfair and doesn't
make sense just because one swap device is swapped on firstly, its
priority will be higher than the one swapped on later.

So in this patchset, change is made to select the swap device round robin
if default priority.  In code, the plist array swap_avail_heads[nid] is
replaced with a plist swap_avail_head which reverts commit a2468cc9bfdf.
Meanwhile, on top of the revert, further change is taken to make any
device w/o specified priority get the same default priority '-1'.  Surely,
swap device with specified priority are always put foremost, this is not
impacted.  If you care about their different accessing speed, then use
'swapon -p xx' to deploy priority for your swap devices.

New behaviour:

swap_avail_list: /* one global available swap device list */
zram0   -> zram1   -> zram2   -> zram3
prio:1     prio:1     prio:1     prio:1

This is the swapon output during the process high pressure vm-scability
being taken, all is selected round robin:
=======================================
[root@hp-dl385g10-03 linux]# swapon
NAME       TYPE      SIZE  USED PRIO
/dev/zram0 partition  16G 12.6G   -1
/dev/zram1 partition  16G 12.6G   -1
/dev/zram2 partition  16G 12.6G   -1
/dev/zram3 partition  16G 12.6G   -1

With the change, we can see about 18% efficiency promotion as below:

vm-scability test:
==================
Test with:
usemem --init-time -O -y -x -n 31 2G (4G memcg, zram as swap)
                           Before:          After:
System time:               637.92 s         526.74 s      (lower is better)
Sum Throughput:            3546.56 MB/s     4207.56 MB/s  (higher is better)
Single process Throughput: 114.40 MB/s      135.72 MB/s   (higher is better)
free latency:              10138455.99 us   6810119.01 us (low is better)

This patch (of 2):

This reverts commit a2468cc9bfdf ("swap: choose swap device according to
numa node").

After this patch, the behaviour will change back to pre-commit
a2468cc9bfdf.  Means the priority will be set from -1 then downwards by
default, and when swapping, it will exhault swap device one by one
according to priority from high to low.  This is preparation work for
later change.

[root@hp-dl385g10-03 ~]# swapon
NAME       TYPE      SIZE   USED PRIO
/dev/zram0 partition  16G    16G   -1
/dev/zram1 partition  16G 966.2M   -2
/dev/zram2 partition  16G     0B   -3
/dev/zram3 partition  16G     0B   -4

Link: https://lkml.kernel.org/r/20251011081624.224202-1-bhe@redhat.com
Link: https://lkml.kernel.org/r/20251011081624.224202-2-bhe@redhat.com
Signed-off-by: Baoquan He <bhe@redhat.com>
Suggested-by: Chris Li <chrisl@kernel.org>
Acked-by: Chris Li <chrisl@kernel.org>Cc:
Cc: Aaron Lu <aaron.lu@intel.com>
Cc: Barry Song <baohua@kernel.org>
Cc: Kairui Song <kasong@tencent.com>
Cc: Kemeng Shi <shikemeng@huaweicloud.com>
Cc: Nhat Pham <nphamcs@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Documentation/admin-guide/mm/swap_numa.rst [deleted file]
include/linux/swap.h
mm/swapfile.c

diff --git a/Documentation/admin-guide/mm/swap_numa.rst b/Documentation/admin-guide/mm/swap_numa.rst
deleted file mode 100644 (file)
index 2e63062..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-===========================================
-Automatically bind swap device to numa node
-===========================================
-
-If the system has more than one swap device and swap device has the node
-information, we can make use of this information to decide which swap
-device to use in get_swap_pages() to get better performance.
-
-
-How to use this feature
-=======================
-
-Swap device has priority and that decides the order of it to be used. To make
-use of automatically binding, there is no need to manipulate priority settings
-for swap devices. e.g. on a 2 node machine, assume 2 swap devices swapA and
-swapB, with swapA attached to node 0 and swapB attached to node 1, are going
-to be swapped on. Simply swapping them on by doing::
-
-       # swapon /dev/swapA
-       # swapon /dev/swapB
-
-Then node 0 will use the two swap devices in the order of swapA then swapB and
-node 1 will use the two swap devices in the order of swapB then swapA. Note
-that the order of them being swapped on doesn't matter.
-
-A more complex example on a 4 node machine. Assume 6 swap devices are going to
-be swapped on: swapA and swapB are attached to node 0, swapC is attached to
-node 1, swapD and swapE are attached to node 2 and swapF is attached to node3.
-The way to swap them on is the same as above::
-
-       # swapon /dev/swapA
-       # swapon /dev/swapB
-       # swapon /dev/swapC
-       # swapon /dev/swapD
-       # swapon /dev/swapE
-       # swapon /dev/swapF
-
-Then node 0 will use them in the order of::
-
-       swapA/swapB -> swapC -> swapD -> swapE -> swapF
-
-swapA and swapB will be used in a round robin mode before any other swap device.
-
-node 1 will use them in the order of::
-
-       swapC -> swapA -> swapB -> swapD -> swapE -> swapF
-
-node 2 will use them in the order of::
-
-       swapD/swapE -> swapA -> swapB -> swapC -> swapF
-
-Similaly, swapD and swapE will be used in a round robin mode before any
-other swap devices.
-
-node 3 will use them in the order of::
-
-       swapF -> swapA -> swapB -> swapC -> swapD -> swapE
-
-
-Implementation details
-======================
-
-The current code uses a priority based list, swap_avail_list, to decide
-which swap device to use and if multiple swap devices share the same
-priority, they are used round robin. This change here replaces the single
-global swap_avail_list with a per-numa-node list, i.e. for each numa node,
-it sees its own priority based list of available swap devices. Swap
-device's priority can be promoted on its matching node's swap_avail_list.
-
-The current swap device's priority is set as: user can set a >=0 value,
-or the system will pick one starting from -1 then downwards. The priority
-value in the swap_avail_list is the negated value of the swap device's
-due to plist being sorted from low to high. The new policy doesn't change
-the semantics for priority >=0 cases, the previous starting from -1 then
-downwards now becomes starting from -2 then downwards and -1 is reserved
-as the promoted value. So if multiple swap devices are attached to the same
-node, they will all be promoted to priority -1 on that node's plist and will
-be used round robin before any other swap devices.
index a4b2648177359c8fab102708d243297e3238d8af..38ca3df68716042946274c18a3a6695dda3b7b65 100644 (file)
@@ -301,16 +301,7 @@ struct swap_info_struct {
        struct work_struct discard_work; /* discard worker */
        struct work_struct reclaim_work; /* reclaim worker */
        struct list_head discard_clusters; /* discard clusters list */
-       struct plist_node avail_lists[]; /*
-                                          * entries in swap_avail_heads, one
-                                          * entry per node.
-                                          * Must be last as the number of the
-                                          * array is nr_node_ids, which is not
-                                          * a fixed value so have to allocate
-                                          * dynamically.
-                                          * And it has to be an array so that
-                                          * plist_for_each_* can work.
-                                          */
+       struct plist_node avail_list;   /* entry in swap_avail_head */
 };
 
 static inline swp_entry_t page_swap_entry(struct page *page)
index 0c2174d6b9248d18c90377979f22a1faf599fe68..4a36ea15de2b7c66dce0c2a0946a49280a7d22e2 100644 (file)
@@ -74,7 +74,7 @@ atomic_long_t nr_swap_pages;
 EXPORT_SYMBOL_GPL(nr_swap_pages);
 /* protected with swap_lock. reading in vm_swap_full() doesn't need lock */
 long total_swap_pages;
-static int least_priority = -1;
+static int least_priority;
 unsigned long swapfile_maximum_size;
 #ifdef CONFIG_MIGRATION
 bool swap_migration_ad_supported;
@@ -103,7 +103,7 @@ static PLIST_HEAD(swap_active_head);
  * is held and the locking order requires swap_lock to be taken
  * before any swap_info_struct->lock.
  */
-static struct plist_head *swap_avail_heads;
+static PLIST_HEAD(swap_avail_head);
 static DEFINE_SPINLOCK(swap_avail_lock);
 
 struct swap_info_struct *swap_info[MAX_SWAPFILES];
@@ -1130,7 +1130,6 @@ done:
 /* SWAP_USAGE_OFFLIST_BIT can only be set by this helper. */
 static void del_from_avail_list(struct swap_info_struct *si, bool swapoff)
 {
-       int nid;
        unsigned long pages;
 
        spin_lock(&swap_avail_lock);
@@ -1159,8 +1158,7 @@ static void del_from_avail_list(struct swap_info_struct *si, bool swapoff)
                        goto skip;
        }
 
-       for_each_node(nid)
-               plist_del(&si->avail_lists[nid], &swap_avail_heads[nid]);
+       plist_del(&si->avail_list, &swap_avail_head);
 
 skip:
        spin_unlock(&swap_avail_lock);
@@ -1169,7 +1167,6 @@ skip:
 /* SWAP_USAGE_OFFLIST_BIT can only be cleared by this helper. */
 static void add_to_avail_list(struct swap_info_struct *si, bool swapon)
 {
-       int nid;
        long val;
        unsigned long pages;
 
@@ -1202,8 +1199,7 @@ static void add_to_avail_list(struct swap_info_struct *si, bool swapon)
                        goto skip;
        }
 
-       for_each_node(nid)
-               plist_add(&si->avail_lists[nid], &swap_avail_heads[nid]);
+       plist_add(&si->avail_list, &swap_avail_head);
 
 skip:
        spin_unlock(&swap_avail_lock);
@@ -1346,16 +1342,14 @@ static bool swap_alloc_fast(swp_entry_t *entry,
 static bool swap_alloc_slow(swp_entry_t *entry,
                            int order)
 {
-       int node;
        unsigned long offset;
        struct swap_info_struct *si, *next;
 
-       node = numa_node_id();
        spin_lock(&swap_avail_lock);
 start_over:
-       plist_for_each_entry_safe(si, next, &swap_avail_heads[node], avail_lists[node]) {
+       plist_for_each_entry_safe(si, next, &swap_avail_head, avail_list) {
                /* Rotate the device and switch to a new cluster */
-               plist_requeue(&si->avail_lists[node], &swap_avail_heads[node]);
+               plist_requeue(&si->avail_list, &swap_avail_head);
                spin_unlock(&swap_avail_lock);
                if (get_swap_device_info(si)) {
                        offset = cluster_alloc_swap_entry(si, order, SWAP_HAS_CACHE);
@@ -1380,7 +1374,7 @@ start_over:
                 * still in the swap_avail_head list then try it, otherwise
                 * start over if we have not gotten any slots.
                 */
-               if (plist_node_empty(&next->avail_lists[node]))
+               if (plist_node_empty(&si->avail_list))
                        goto start_over;
        }
        spin_unlock(&swap_avail_lock);
@@ -2709,25 +2703,11 @@ static int setup_swap_extents(struct swap_info_struct *sis, sector_t *span)
        return generic_swapfile_activate(sis, swap_file, span);
 }
 
-static int swap_node(struct swap_info_struct *si)
-{
-       struct block_device *bdev;
-
-       if (si->bdev)
-               bdev = si->bdev;
-       else
-               bdev = si->swap_file->f_inode->i_sb->s_bdev;
-
-       return bdev ? bdev->bd_disk->node_id : NUMA_NO_NODE;
-}
-
 static void setup_swap_info(struct swap_info_struct *si, int prio,
                            unsigned char *swap_map,
                            struct swap_cluster_info *cluster_info,
                            unsigned long *zeromap)
 {
-       int i;
-
        if (prio >= 0)
                si->prio = prio;
        else
@@ -2737,16 +2717,7 @@ static void setup_swap_info(struct swap_info_struct *si, int prio,
         * low-to-high, while swap ordering is high-to-low
         */
        si->list.prio = -si->prio;
-       for_each_node(i) {
-               if (si->prio >= 0)
-                       si->avail_lists[i].prio = -si->prio;
-               else {
-                       if (swap_node(si) == i)
-                               si->avail_lists[i].prio = 1;
-                       else
-                               si->avail_lists[i].prio = -si->prio;
-               }
-       }
+       si->avail_list.prio = -si->prio;
        si->swap_map = swap_map;
        si->cluster_info = cluster_info;
        si->zeromap = zeromap;
@@ -2924,10 +2895,7 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
                plist_for_each_entry_continue(si, &swap_active_head, list) {
                        si->prio++;
                        si->list.prio--;
-                       for_each_node(nid) {
-                               if (si->avail_lists[nid].prio != 1)
-                                       si->avail_lists[nid].prio--;
-                       }
+                       si->avail_list.prio--;
                }
                least_priority++;
        }
@@ -3168,9 +3136,8 @@ static struct swap_info_struct *alloc_swap_info(void)
        struct swap_info_struct *p;
        struct swap_info_struct *defer = NULL;
        unsigned int type;
-       int i;
 
-       p = kvzalloc(struct_size(p, avail_lists, nr_node_ids), GFP_KERNEL);
+       p = kvzalloc(sizeof(struct swap_info_struct), GFP_KERNEL);
        if (!p)
                return ERR_PTR(-ENOMEM);
 
@@ -3209,8 +3176,7 @@ static struct swap_info_struct *alloc_swap_info(void)
        }
        p->swap_extent_root = RB_ROOT;
        plist_node_init(&p->list, 0);
-       for_each_node(i)
-               plist_node_init(&p->avail_lists[i], 0);
+       plist_node_init(&p->avail_list, 0);
        p->flags = SWP_USED;
        spin_unlock(&swap_lock);
        if (defer) {
@@ -3467,9 +3433,6 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
 
-       if (!swap_avail_heads)
-               return -ENOMEM;
-
        si = alloc_swap_info();
        if (IS_ERR(si))
                return PTR_ERR(si);
@@ -4079,7 +4042,6 @@ static bool __has_usable_swap(void)
 void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
 {
        struct swap_info_struct *si, *next;
-       int nid = folio_nid(folio);
 
        if (!(gfp & __GFP_IO))
                return;
@@ -4098,8 +4060,8 @@ void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
                return;
 
        spin_lock(&swap_avail_lock);
-       plist_for_each_entry_safe(si, next, &swap_avail_heads[nid],
-                                 avail_lists[nid]) {
+       plist_for_each_entry_safe(si, next, &swap_avail_head,
+                                 avail_list) {
                if (si->bdev) {
                        blkcg_schedule_throttle(si->bdev->bd_disk, true);
                        break;
@@ -4111,18 +4073,6 @@ void __folio_throttle_swaprate(struct folio *folio, gfp_t gfp)
 
 static int __init swapfile_init(void)
 {
-       int nid;
-
-       swap_avail_heads = kmalloc_array(nr_node_ids, sizeof(struct plist_head),
-                                        GFP_KERNEL);
-       if (!swap_avail_heads) {
-               pr_emerg("Not enough memory for swap heads, swap is disabled\n");
-               return -ENOMEM;
-       }
-
-       for_each_node(nid)
-               plist_head_init(&swap_avail_heads[nid]);
-
        swapfile_maximum_size = arch_max_swapfile_size();
 
        /*