Migrate Pages in lieu of discard
Subsystems (12)
v3 cover Migrate Pages in lieu of discard
todo:
Changes since (https://lwn.net/Articles/824830/):
* Use higher-level migrate_pages() API approach from Yang Shi's earlier patches.
* made sure to actually check node_reclaim_mode's new bit
* disabled migration entirely before introducing RECLAIM_MIGRATE
* Replace GFP_NOWAIT with explicit __GFP_KSWAPD_RECLAIM and comment why we want that.
* Comment on effects of that keep multiple source nodes from sharing target nodes
The full series is also available here:
https://github.com/hansendc/linux/tree/automigrate-20200818
--
We're starting to see systems with more and more kinds of memory such as Intel's implementation of persistent memory.
Let's say you have a system with some DRAM and some persistent memory. Today, once DRAM fills up, reclaim will start and some of the DRAM contents will be thrown out. Allocations will, at some point, start falling over to the slower persistent memory.
That has two nasty properties. First, the newer allocations can end up in the slower persistent memory. Second, reclaimed data in DRAM are just discarded even if there are gobs of space in persistent memory that could be used.
This set implements a solution to these problems. At the end of the reclaim process in shrink_page_list() just before the last page refcount is dropped, the page is migrated to persistent memory instead of being dropped.
While I've talked about a DRAM/PMEM pairing, this approach would function in any environment where memory tiers exist.
This is not perfect. It "strands" pages in slower memory and never brings them back to fast DRAM. Other things need to be built to promote hot pages back to DRAM.
This is also all based on an upstream mechanism that allows persistent memory to be onlined and used as if it were volatile:
http://lkml.kernel.org/r/[email protected]
== Open Issues ==
* For cpusets and memory policies that restrict allocations to PMEM, is it OK to demote to PMEM? Do we need a cgroup- level API to opt-in or opt-out of these migrations?
* Migration failures will result in pages being unreclaimable. Need to be able to fall back to normal reclaim.
--
Dave Hansen (5):
mm/numa: node demotion data structure and lookup mm/vmscan: Attempt to migrate page in lieu of discard mm/numa: automatically generate node migration order mm/vmscan: never demote for memcg reclaim mm/numa: new reclaim mode to enable reclaim-based migration
Keith Busch (2):
mm/migrate: Defer allocating new page until needed mm/vmscan: Consider anonymous pages without swap
Yang Shi (1):
mm/vmscan: add page demotion counter
Documentation/admin-guide/sysctl/vm.rst | 9 include/linux/migrate.h | 6 include/linux/node.h | 9 include/linux/vm_event_item.h | 2 include/trace/events/migrate.h | 3 mm/debug.c | 1 mm/internal.h | 1 mm/migrate.c | 400 ++++++++++++++++++++++++++------ mm/page_alloc.c | 2 mm/vmscan.c | 88 ++++++- mm/vmstat.c | 2 11 files changed, 439 insertions(+), 84 deletions(-)
RFC PATCH v3 1/9 mm/numa: node demotion data structure and lookup
From: Dave Hansen <[email protected]>
Prepare for the kernel to auto-migrate pages to other memory nodes with a user defined node migration table. This allows creating single migration target for each NUMA node to enable the kernel to do NUMA page migrations instead of simply reclaiming colder pages. A node with no target is a "terminal node", so reclaim acts normally there. The migration target does not fundamentally _need_ to be a single node, but this implementation starts there to limit complexity.
If you consider the migration path as a graph, cycles (loops) in the graph are disallowed. This avoids wasting resources by constantly migrating (A->B, B->A, A->B ...). The expectation is that cycles will never be allowed.
--
changes in July 2020:
- Remove loop from next_demotion_node() and get_online_mems(). This means that the node returned by next_demotion_node() might now be offline, but the worst case is that the allocation fails. That's fine since it is transient.
---
b/mm/migrate.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
mm/migrate.c renamed from mm/migrate.c~0006-node-Define-and-export-memory-migration-path +16 -0
@@ -1160,6 +1160,22 @@ out:return rc; } +static int node_demotion[MAX_NUMNODES ] = {[0 ...MAX_NUMNODES -1 ] =NUMA_NO_NODE }; + +/** +* next_demotion_node() - Get the next node in the demotion path +* @node: The starting node to lookup the next node +* +* @returns: node id for next memory node in the demotion path hierarchy +* from @node; NUMA_NO_NODE if @node is terminal. This does not keep +* @node online or guarantee that it *continues* to be the next demotion +* target. +*/ +int next_demotion_node (int node) +{ +return node_demotion[node]; +} +/* * Obtain the lock on page, remove all ptes and migrate the page * to the newly allocated page in newpage.
_
RFC PATCH v3 2/9 mm/numa: automatically generate node migration order
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
When memory fills up on a node, memory contents can be automatically migrated to another node. The biggest problems are knowing when to migrate and to where the migration should be targeted.
The most straightforward way to generate the "to where" list would be to follow the page allocator fallback lists. Those lists already tell us if memory is full where to look next. It would also be logical to move memory in that order.
But, the allocator fallback lists have a fatal flaw: most nodes appear in all the lists. This would potentially lead to migration cycles (A->B, B->A, A->B, ...).
Instead of using the allocator fallback lists directly, keep a separate node migration ordering. But, reuse the same data used to generate page allocator fallback in the first place:find_next_best_node().
This means that the firmware data used to populate node distances essentially dictates the ordering for now. It should also be architecture-neutral since all NUMA architectures have a working find_next_best_node().
The protocol for node_demotion[] access and writing is not standard. It has no specific locking and is intended to be read locklessly. Readers must take care to avoid observing changes that appear incoherent. This was done so that node_demotion[] locking has no chance of becoming a bottleneck on large systems with lots of CPUs in direct reclaim.
This code is unused for now. It will be called later in the series.
---
b/mm/internal.h | 1 b/mm/migrate.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++- b/mm/page_alloc.c | 2 3 files changed, 138 insertions(+), 2 deletions(-)
mm/internal.h renamed from mm/internal.h~auto-setup-default-migration-path-from-firmware +1 -0
@@ -203,6 +203,7 @@ extern int user_min_free_kbytes;extern void zone_pcp_update (struct zone *zone);extern void zone_pcp_reset (struct zone *zone); +extern int find_next_best_node (int node,nodemask_t *used_node_mask);#if definedCONFIG_COMPACTION || definedCONFIG_CMA
mm/migrate.c renamed from mm/migrate.c~auto-setup-default-migration-path-from-firmware +136 -1
152 lines not on this page · raw message
mm/page_alloc.c renamed from mm/page_alloc.c~auto-setup-default-migration-path-from-firmware +1 -1
@@ -5607,7 +5607,7 @@ static int node_load[MAX_NUMNODES];* * Return: node id of the found node or %NUMA_NO_NODE if no node is found. */ -static int find_next_best_node (int node,nodemask_t *used_node_mask) +int find_next_best_node (int node,nodemask_t *used_node_mask) {int n, val;int min_val =INT_MAX ;
_
Re: [RFC][PATCH 2/9] mm/numa: automatically generate node migration order
Quoted 168 lines · Dave Hansen
From: Dave Hansen <[email protected]>
When memory fills up on a node, memory contents can be automatically migrated to another node. The biggest problems are knowing when to migrate and to where the migration should be targeted.
The most straightforward way to generate the "to where" list would be to follow the page allocator fallback lists. Those lists already tell us if memory is full where to look next. It would also be logical to move memory in that order.
But, the allocator fallback lists have a fatal flaw: most nodes appear in all the lists. This would potentially lead to migration cycles (
A->B,B->A,A->B, ...).Instead of using the allocator fallback lists directly, keep a separate node migration ordering. But, reuse the same data used to generate page allocator fallback in the first place:
find_next_best_node().This means that the firmware data used to populate node distances essentially dictates the ordering for now. It should also be architecture-neutral since all NUMA architectures have a working
find_next_best_node().The protocol for node_demotion[] access and writing is not standard. It has no specific locking and is intended to be read locklessly. Readers must take care to avoid observing changes that appear incoherent. This was done so that node_demotion[] locking has no chance of becoming a bottleneck on large systems with lots of CPUs in direct reclaim.
This code is unused for now. It will be called later in the series.
Signed-off-by Dave Hansen <[email protected]>Cc Yang Shi <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/mm/internal.h | 1 b/mm/migrate.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++- b/mm/page_alloc.c | 2 3 files changed, 138 insertions(+), 2 deletions(-)diff -puN
mm/internal.h~auto-setup-default-migration-path-from-firmwaremm/internal.hmm/internal.h 2020-08-18 11:36:48.968583188 -0700 renamed from mm/internal.h~auto-setup-default-migration-path-from-firmware 2020-08-18 11:36:48.960583188 -0700 +1
-0@@ -203,6 +203,7 @@ extern int user_min_free_kbytes; extern void zone_pcp_update(struct zone *zone); extern void zone_pcp_reset(struct zone *zone); +extern int find_next_best_node(int node, nodemask_t *used_node_mask); #if defined CONFIG_COMPACTION || defined CONFIG_CMAdiff -puN
mm/migrate.c~auto-setup-default-migration-path-from-firmwaremm/migrate.cmm/migrate.c 2020-08-18 11:36:48.970583188 -0700 renamed from mm/migrate.c~auto-setup-default-migration-path-from-firmware 2020-08-18 11:36:48.962583188 -0700 +85
-1@@ -1160,6 +1160,10 @@ out: return rc; } +/* + * Writes to this array occur without locking. READ_ONCE() + * is recommended for readers to ensure consistent reads. + */ static int node_demotion[MAX_NUMNODES] = {[0 ... MAX_NUMNODES - 1] = NUMA_NO_NODE}; /**@@ -1173,7 +1177,13 @@ static int node_demotion[MAX_NUMNODES] = */ int next_demotion_node(int node) { - return node_demotion[node]; + /* + * node_demotion[] is updated without excluding + * this function from running. READ_ONCE() avoids + * reading multiple, inconsistent 'node' values + * during an update. + */ + return READ_ONCE(node_demotion[node]); } /*@@ -3001,3 +3011,128 @@ void migrate_vma_finalize(struct migrate } EXPORT_SYMBOL(migrate_vma_finalize); #endif /* CONFIG_DEVICE_PRIVATE */ + +/* Disable reclaim-based migration. */ +static void disable_all_migrate_targets(void) +{ + int node; + + for_each_online_node(node) + node_demotion[node] = NUMA_NO_NODE; +} + +/* + * Find an automatic demotion target for 'node'. + * Failing here is OK. It might just indicate + * being at the end of a chain. + */ +static int establish_migrate_target(int node, nodemask_t *used) +{ + int migration_target; + + /* + * Can not set a migration target on a + * node with it already set. + * + * No need for READ_ONCE() here since this + * in the write path for node_demotion[]. + * This should be the only thread writing. + */ + if (node_demotion[node] != NUMA_NO_NODE) + return NUMA_NO_NODE; + + migration_target = find_next_best_node(node, used); + if (migration_target == NUMA_NO_NODE) + return NUMA_NO_NODE; + + node_demotion[node] = migration_target; + + return migration_target; +} + +/* + * When memory fills up on a node, memory contents can be + * automatically migrated to another node instead of + * discarded at reclaim. + * + * Establish a "migration path" which will start at nodes + * with CPUs and will follow the priorities used to build the + * page allocator zonelists. + * + * The difference here is that cycles must be avoided. If + * node0 migrates to node1, then neither node1, nor anything + * node1 migrates to can migrate to node0. + * + * This function can run simultaneously with readers of + * node_demotion[]. However, it can not run simultaneously + * with itself. Exclusion is provided by memory hotplug events + * being single-threaded. + */ +void __set_migration_target_nodes(void) +{ + nodemask_t next_pass = NODE_MASK_NONE; + nodemask_t this_pass = NODE_MASK_NONE; + nodemask_t used_targets = NODE_MASK_NONE; + int node; + + /* + * Avoid any oddities like cycles that could occur + * from changes in the topology. This will leave + * a momentary gap when migration is disabled. + */ + disable_all_migrate_targets(); + + /* + * Ensure that the "disable" is visible across the system. + * Readers will see either a combination of before+disable
mm/migrate.c 2020-08-18 11:36:48.970583188 -0700 void migrate_vma_finalize(struct migrate · line 3097
Quoted 10 lines
mm/migrate.c 2020-08-18 11:36:48.970583188 -0700 renamed from mm/migrate.c~auto-setup-default-migration-path-from-firmware 2020-08-18 11:36:48.962583188 -0700 +10
-0+ * state or disable+after. They will never see before and + * after state together. + * + * The before+after state together might have cycles and + * could cause readers to do things like loop until this + * function finishes. This ensures they can only see a + * single "bad" read and would, for instance, only loop + * once. + */ + smp_wmb();
Sounds better to move sp_wmb() into disable_all_migrate_targets()? I thought we need the barrier everytime disable_all_migrate_targets() is called, if so why not put them together to avoid overlooking?
Quoted 54 lines
mm/migrate.c 2020-08-18 11:36:48.970583188 -0700 renamed from mm/migrate.c~auto-setup-default-migration-path-from-firmware 2020-08-18 11:36:48.962583188 -0700 +41
-0+ + /* + * Allocations go close to CPUs, first. Assume that + * the migration path starts at the nodes with CPUs. + */ + next_pass = node_states[N_CPU]; +again: + this_pass = next_pass; + next_pass = NODE_MASK_NONE; + /* + * To avoid cycles in the migration "graph", ensure + * that migration sources are not future targets by + * setting them in 'used_targets'. Do this only + * once per pass so that multiple source nodes can + * share a target node. + * + * 'used_targets' will become unavailable in future + * passes. This limits some opportunities for + * multiple source nodes to share a desintation. + */ + nodes_or(used_targets, used_targets, this_pass); + for_each_node_mask(node, this_pass) { + int target_node = establish_migrate_target(node, &used_targets); + + if (target_node == NUMA_NO_NODE) + continue; + + /* Visit targets from this pass in the next pass: */ + node_set(target_node, next_pass); + } + /* Is another pass necessary? */ + if (!nodes_empty(next_pass)) + goto again; +} + +void set_migration_target_nodes(void) +{ + get_online_mems(); + __set_migration_target_nodes(); + put_online_mems(); +}diff -puN
mm/page_alloc.c~auto-setup-default-migration-path-from-firmwaremm/page_alloc.cmm/page_alloc.c 2020-08-18 11:36:48.972583188 -0700 renamed from mm/page_alloc.c~auto-setup-default-migration-path-from-firmware 2020-08-18 11:36:48.964583188 -0700 +1
-1@@ -5607,7 +5607,7 @@ static int node_load[MAX_NUMNODES]; * * Return: node id of the found node or %NUMA_NO_NODE if no node is found. */ -static int find_next_best_node(int node, nodemask_t *used_node_mask) +int find_next_best_node(int node, nodemask_t *used_node_mask) { int n, val; int min_val = INT_MAX;_
RFC PATCH v3 3/9 mm/migrate: update migration order during on hotplug events
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
Reclaim-based migration is attempting to optimize data placement in memory based on the system topology. If the system changes, so must the migration ordering.
The implementation here is pretty simple and entirely unoptimized. On any memory or CPU hotplug events, assume that a node was added or removed and recalculate all migration targets. This ensures that the node_demotion[] array is always ready to be used in case the new reclaim mode is enabled.
This recalculation is far from optimal, most glaringly that it does not even attempt to figure out if nodes are actually coming or going. But, given the expected paucity of hotplug events, this should be fine.
---
b/mm/migrate.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+)
mm/migrate.c renamed from mm/migrate.c~enable-numa-demotion +93 -0
@@ -49,6 +49,7 @@#include <linux/sched/mm.h> #include <linux/ptrace.h> #include <linux/oom.h> +#include <linux/memory.h> #include <asm/tlbflush.h>
@@ -3130,9 +3131,101 @@ again: goto again; } +/* +* For callers that do not hold get_online_mems() already. +*/ void set_migration_target_nodes (void ) {get_online_mems ();__set_migration_target_nodes ();put_online_mems (); } + +/* +* React to hotplug events that might affect the migration targes +* like events that online or offline NUMA nodes. +* +* The ordering is also currently dependent on which nodes have +* CPUs. That means we need CPU on/offline notification too. +*/ +static int migration_online_cpu (unsigned int cpu) +{ +set_migration_target_nodes (); +return 0 ; +} + +static int migration_offline_cpu (unsigned int cpu) +{ +set_migration_target_nodes (); +return 0 ; +} + +/* +* This leaves migrate-on-reclaim transiently disabled +* between the MEM_GOING_OFFLINE and MEM_OFFLINE events. +* This runs reclaim-based micgration is enabled or not. +* This ensures that the user can turn reclaim-based +* migration at any time without needing to recalcuate +* migration targets. +* +* These callbacks already hold get_online_mems(). That +* is why __set_migration_target_nodes() can be used as +* opposed to set_migration_target_nodes(). +*/ +#if defined(CONFIG_MEMORY_HOTPLUG ) +static int __meminit migrate_on_reclaim_callback (struct notifier_block *self, +unsigned long action,void *arg) +{ +switch (action) { +case MEM_GOING_OFFLINE : +/* +* Make sure there are not transient states where +* an offline node is a migration target. This +* will leave migration disabled until the offline +* completes and the MEM_OFFLINE case below runs. +*/ +disable_all_migrate_targets (); +break ; +case MEM_OFFLINE : +case MEM_ONLINE : +/* +* Recalculate the target nodes once the node +* reaches its final state (online or offline). +*/ +__set_migration_target_nodes (); +break ; +case MEM_CANCEL_OFFLINE : +/* +* MEM_GOING_OFFLINE disabled all the migration +* targets. Reenable them. +*/ +__set_migration_target_nodes (); +break ; +case MEM_GOING_ONLINE : +case MEM_CANCEL_ONLINE : +break ; + } + +return notifier_from_errno (0 ); +} + +static int __init migrate_on_reclaim_init (void ) +{ +int ret; + + ret =cpuhp_setup_state (CPUHP_AP_ONLINE_DYN ,"migrate on reclaim" , + migration_online_cpu, + migration_offline_cpu); +/* +* In the unlikely case that this fails, the automatic +* migration targets may become suboptimal for nodes +* where N_CPU changes. With such a small impact in a +* rare case, do not bother trying to do anything special. +*/ +WARN_ON (ret <0 ); + +hotplug_memory_notifier (migrate_on_reclaim_callback,100 ); +return 0 ; +} +late_initcall (migrate_on_reclaim_init); +#endif /* CONFIG_MEMORY_HOTPLUG */
_
Re: [RFC][PATCH 3/9] mm/migrate: update migration order during on hotplug events
Quoted 46 lines · Dave Hansen
From: Dave Hansen <[email protected]>
Reclaim-based migration is attempting to optimize data placement in memory based on the system topology. If the system changes, so must the migration ordering.
The implementation here is pretty simple and entirely unoptimized. On any memory or CPU hotplug events, assume that a node was added or removed and recalculate all migration targets. This ensures that the node_demotion[] array is always ready to be used in case the new reclaim mode is enabled.
This recalculation is far from optimal, most glaringly that it does not even attempt to figure out if nodes are actually coming or going. But, given the expected paucity of hotplug events, this should be fine.
Signed-off-by Dave Hansen <[email protected]>Cc Yang Shi <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/mm/migrate.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+)diff -puN
mm/migrate.c~enable-numa-demotionmm/migrate.cmm/migrate.c 2020-08-18 11:36:50.197583185 -0700 renamed from mm/migrate.c~enable-numa-demotion 2020-08-18 11:36:50.193583185 -0700 +3
-0@@ -49,6 +49,7 @@ #include <linux/sched/mm.h> #include <linux/ptrace.h> #include <linux/oom.h> +#include <linux/memory.h> #include <asm/tlbflush.h>@@ -3130,9 +3131,101 @@ again: goto again; } +/* + * For callers that do not hold get_online_mems() already.
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 again: · line 3145
Quoted 10 lines
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 renamed from mm/migrate.c~enable-numa-demotion 2020-08-18 11:36:50.193583185 -0700 +4
-0+ */ void set_migration_target_nodes(void) { get_online_mems(); __set_migration_target_nodes(); put_online_mems(); } + +/* + * React to hotplug events that might affect the migration targes
s/targes/targets
Quoted 11 lines
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 renamed from mm/migrate.c~enable-numa-demotion 2020-08-18 11:36:50.193583185 -0700 +11
-0+ * like events that online or offline NUMA nodes. + * + * The ordering is also currently dependent on which nodes have + * CPUs. That means we need CPU on/offline notification too. + */ +static int migration_online_cpu(unsigned int cpu) +{ + set_migration_target_nodes(); + return 0; +} +
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 again: · line 3166
Quoted 10 lines
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 renamed from mm/migrate.c~enable-numa-demotion 2020-08-18 11:36:50.193583185 -0700 +10
-0+static int migration_offline_cpu(unsigned int cpu) +{ + set_migration_target_nodes(); + return 0; +} + +/* + * This leaves migrate-on-reclaim transiently disabled + * between the MEM_GOING_OFFLINE and MEM_OFFLINE events. + * This runs reclaim-based micgration is enabled or not.
s/micgration/migration
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 again: · line 3168
Quoted 2 lines
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 renamed from mm/migrate.c~enable-numa-demotion 2020-08-18 11:36:50.193583185 -0700 +2
-0+ * This ensures that the user can turn reclaim-based + * migration at any time without needing to recalcuate
s/recalcuate/recalculate
Quoted 64 lines
mm/migrate.c 2020-08-18 11:36:50.197583185 -0700 renamed from mm/migrate.c~enable-numa-demotion 2020-08-18 11:36:50.193583185 -0700 +63
-0+ * migration targets. + * + * These callbacks already hold get_online_mems(). That + * is why __set_migration_target_nodes() can be used as + * opposed to set_migration_target_nodes(). + */ +#if defined(CONFIG_MEMORY_HOTPLUG) +static int __meminit migrate_on_reclaim_callback(struct notifier_block *self, + unsigned long action, void *arg) +{ + switch (action) { + case MEM_GOING_OFFLINE: + /* + * Make sure there are not transient states where + * an offline node is a migration target. This + * will leave migration disabled until the offline + * completes and the MEM_OFFLINE case below runs. + */ + disable_all_migrate_targets(); + break; + case MEM_OFFLINE: + case MEM_ONLINE: + /* + * Recalculate the target nodes once the node + * reaches its final state (online or offline). + */ + __set_migration_target_nodes(); + break; + case MEM_CANCEL_OFFLINE: + /* + * MEM_GOING_OFFLINE disabled all the migration + * targets. Reenable them. + */ + __set_migration_target_nodes(); + break; + case MEM_GOING_ONLINE: + case MEM_CANCEL_ONLINE: + break; + } + + return notifier_from_errno(0); +} + +static int __init migrate_on_reclaim_init(void) +{ + int ret; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "migrate on reclaim", + migration_online_cpu, + migration_offline_cpu); + /* + * In the unlikely case that this fails, the automatic + * migration targets may become suboptimal for nodes + * where N_CPU changes. With such a small impact in a + * rare case, do not bother trying to do anything special. + */ + WARN_ON(ret < 0); + + hotplug_memory_notifier(migrate_on_reclaim_callback, 100); + return 0; +} +late_initcall(migrate_on_reclaim_init); +#endif /* CONFIG_MEMORY_HOTPLUG */_
RFC PATCH v3 4/9 mm/migrate: make migrate_pages() return nr_succeeded
In reply to Dave HansenFrom: Yang Shi <[email protected]>
The migrate_pages() returns the number of pages that were not migrated, or an error code. When returning an error code, there is no way to know how many pages were migrated or not migrated.
In the following patch, migrate_pages() is used to demote pages to PMEM node, we need account how many pages are reclaimed (demoted) since page reclaim behavior depends on this. Add *nr_succeeded parameter to make migrate_pages() return how many pages are demoted successfully for all cases.
---
b/include/linux/migrate.h | 5 +++-- b/mm/compaction.c | 3 ++- b/mm/gup.c | 4 +++- b/mm/memory-failure.c | 7 +++++-- b/mm/memory_hotplug.c | 4 +++- b/mm/mempolicy.c | 7 +++++-- b/mm/migrate.c | 18 ++++++++++-------- b/mm/page_alloc.c | 4 +++- 8 files changed, 34 insertions(+), 18 deletions(-)
include/linux/migrate.h renamed from include/linux/migrate.h~migrate_pages-add-success-return +3 -2
@@ -66,7 +66,8 @@ extern int migrate_page(struct address_s struct page *newpage, struct page *page, enum migrate_mode mode);extern int migrate_pages (struct list_head *l,new_page_t new,free_page_t free, -unsigned long private,enum migrate_mode mode,int reason); +unsigned long private,enum migrate_mode mode,int reason, +unsigned int *nr_succeeded);extern int isolate_movable_page (struct page *page,isolate_mode_t mode);extern void putback_movable_page (struct page *page);
@@ -83,7 +84,7 @@ extern int migrate_page_move_mapping(strstatic inline void putback_movable_pages (struct list_head *l) {}static inline int migrate_pages (struct list_head *l,new_page_t new,free_page_t free,unsigned long private,enum migrate_mode mode, -int reason) +int reason,unsigned int *nr_succeeded) {return -ENOSYS ; }static inline int isolate_movable_page (struct page *page,isolate_mode_t mode) {return -EBUSY ; }
mm/compaction.c renamed from mm/compaction.c~migrate_pages-add-success-return +2 -1
@@ -2088,6 +2088,7 @@ compact_zone(struct compact_control *cc,unsigned long last_migrated_pfn;const bool sync = cc->mode !=MIGRATE_ASYNC ;bool update_cached; +unsigned int nr_succeeded =0 ;/* * These counters track activities during zone compaction. Initialize
@@ -2206,7 +2207,7 @@ compact_zone(struct compact_control *cc, err =migrate_pages (&cc->migratepages, compaction_alloc, compaction_free, (unsigned long )cc, cc->mode, -MR_COMPACTION ); +MR_COMPACTION , &nr_succeeded);trace_mm_compaction_migratepages (cc->nr_migratepages, err, &cc->migratepages);
mm/gup.c renamed from mm/gup.c~migrate_pages-add-success-return +3 -1
@@ -1671,6 +1671,7 @@ static long check_and_migrate_cma_pages(unsigned long step;bool drain_allow = true;bool migrate_allow = true; +unsigned int nr_succeeded =0 ;LIST_HEAD (cma_page_list);long ret = nr_pages;
@@ -1719,7 +1720,8 @@ check_again:put_page (pages[i]);if (migrate_pages (&cma_page_list, new_non_cma_page, -NULL ,0 ,MIGRATE_SYNC ,MR_CONTIG_RANGE )) { +NULL ,0 ,MIGRATE_SYNC ,MR_CONTIG_RANGE , + &nr_succeeded)) {/* * some of the pages failed migration. Do get_user_pages * without migration.
mm/memory-failure.c renamed from mm/memory-failure.c~migrate_pages-add-success-return +5 -2
@@ -1721,6 +1721,7 @@ static int soft_offline_huge_page(structint ret;unsigned long pfn =page_to_pfn (page);struct page *hpage =compound_head (page); +unsigned int nr_succeeded =0 ;LIST_HEAD (pagelist);/*
@@ -1748,7 +1749,7 @@ static int soft_offline_huge_page(struct } ret =migrate_pages (&pagelist, new_page,NULL ,MPOL_MF_MOVE_ALL , -MIGRATE_SYNC ,MR_MEMORY_FAILURE ); +MIGRATE_SYNC ,MR_MEMORY_FAILURE , &nr_succeeded);if (ret) {pr_info ("soft offline: %#lx: hugepage migration failed %d, type %lx (%pGp)\n" , pfn, ret, page->flags, &page->flags);
@@ -1779,6 +1780,7 @@ static int __soft_offline_page(struct pa {int ret;unsigned long pfn =page_to_pfn (page); +unsigned int nr_succeeded =0 ;/* * Check PageHWPoison again inside page lock because PageHWPoison
@@ -1838,7 +1840,8 @@ static int __soft_offline_page(struct pa page_is_file_lru(page));list_add (&page->lru, &pagelist); ret =migrate_pages (&pagelist, new_page,NULL ,MPOL_MF_MOVE_ALL , -MIGRATE_SYNC ,MR_MEMORY_FAILURE ); +MIGRATE_SYNC ,MR_MEMORY_FAILURE , + &nr_succeeded);if (ret) {if (!list_empty (&pagelist))putback_movable_pages (&pagelist);
mm/memory_hotplug.c renamed from mm/memory_hotplug.c~migrate_pages-add-success-return +3 -1
@@ -1282,6 +1282,7 @@ do_migrate_range(unsigned long start_pfnunsigned long pfn;struct page *page;int ret =0 ; +unsigned int nr_succeeded =0 ;LIST_HEAD (source);for (pfn = start_pfn; pfn < end_pfn; pfn++) {
@@ -1338,7 +1339,8 @@ do_migrate_range(unsigned long start_pfnif (!list_empty (&source)) {/* Allocate a new page from the nearest neighbor node */ ret =migrate_pages (&source, new_node_page,NULL ,0 , -MIGRATE_SYNC ,MR_MEMORY_HOTPLUG ); +MIGRATE_SYNC ,MR_MEMORY_HOTPLUG , + &nr_succeeded);if (ret) {list_for_each_entry (page, &source, lru) { pr_warn("migrating pfn %lx failed ret:%d ",
mm/mempolicy.c renamed from mm/mempolicy.c~migrate_pages-add-success-return +5 -2
@@ -1096,6 +1096,7 @@ static int migrate_to_node(struct mm_strnodemask_t nmask;LIST_HEAD (pagelist);int err =0 ; +unsigned int nr_succeeded =0 ;nodes_clear (nmask);node_set (source, nmask);
@@ -1111,7 +1112,7 @@ static int migrate_to_node(struct mm_strif (!list_empty (&pagelist)) { err =migrate_pages (&pagelist, alloc_new_node_page,NULL , dest, -MIGRATE_SYNC ,MR_SYSCALL ); +MIGRATE_SYNC ,MR_SYSCALL , &nr_succeeded);if (err)putback_movable_pages (&pagelist); }
@@ -1288,6 +1289,7 @@ static long do_mbind(unsigned long start nodemask_t *nmask, unsigned long flags) {struct mm_struct *mm = current->mm; +unsigned int nr_succeeded =0 ;struct mempolicy *new;unsigned long end;int err;
@@ -1369,7 +1371,8 @@ static long do_mbind(unsigned long startif (!list_empty (&pagelist)) {WARN_ON_ONCE (flags &MPOL_MF_LAZY ); nr_failed =migrate_pages (&pagelist, new_page,NULL , - start,MIGRATE_SYNC ,MR_MEMPOLICY_MBIND ); + start,MIGRATE_SYNC ,MR_MEMPOLICY_MBIND , + &nr_succeeded);if (nr_failed)putback_movable_pages (&pagelist); }
mm/migrate.c renamed from mm/migrate.c~migrate_pages-add-success-return +10 -8
@@ -1432,6 +1432,7 @@ out:* @mode: The migration mode that specifies the constraints for * page migration, if any. * @reason: The reason for page migration. +* @nr_succeeded: The number of pages migrated successfully. * * The function returns after 10 attempts or if no pages are movable any more * because the list has become empty or no retryable pages exist any more.
@@ -1442,11 +1443,10 @@ out:*/ int migrate_pages (struct list_head *from,new_page_t get_new_page,free_page_t put_new_page,unsigned long private, -enum migrate_mode mode,int reason) +enum migrate_mode mode,int reason,unsigned int *nr_succeeded) {int retry =1 ;int nr_failed =0 ; -int nr_succeeded =0 ;int pass =0 ;struct page *page;struct page *page2;
@@ -1500,7 +1500,7 @@ retry: retry++;break ;case MIGRATEPAGE_SUCCESS : - nr_succeeded++; + (*nr_succeeded)++;break ;default :/*
@@ -1517,11 +1517,11 @@ retry: nr_failed += retry; rc = nr_failed; out: -if (nr_succeeded) -count_vm_events (PGMIGRATE_SUCCESS , nr_succeeded); +if (*nr_succeeded) +count_vm_events (PGMIGRATE_SUCCESS , *nr_succeeded);if (nr_failed)count_vm_events (PGMIGRATE_FAIL , nr_failed); -trace_mm_migrate_pages (nr_succeeded, nr_failed, mode, reason); +trace_mm_migrate_pages (*nr_succeeded, nr_failed, mode, reason);if (!swapwrite) current->flags &= ~PF_SWAPWRITE ;
@@ -1546,9 +1546,10 @@ static int do_move_pages_to_node(struct struct list_head *pagelist, int node) { int err; +unsigned int nr_succeeded =0 ; err =migrate_pages (pagelist, alloc_new_node_page,NULL , node, -MIGRATE_SYNC ,MR_SYSCALL ); +MIGRATE_SYNC ,MR_SYSCALL , &nr_succeeded);if (err)putback_movable_pages (pagelist);return err;
@@ -2006,6 +2007,7 @@ int migrate_misplaced_page(struct page *pg_data_t *pgdat =NODE_DATA (node);int isolated;int nr_remaining; +unsigned int nr_succeeded =0 ;LIST_HEAD (migratepages);/*
@@ -2030,7 +2032,7 @@ int migrate_misplaced_page(struct page *list_add (&page->lru, &migratepages); nr_remaining =migrate_pages (&migratepages, alloc_misplaced_dst_page,NULL , node,MIGRATE_ASYNC , -MR_NUMA_MISPLACED ); +MR_NUMA_MISPLACED , &nr_succeeded);if (nr_remaining) {if (!list_empty (&migratepages)) {list_del (&page->lru);
mm/page_alloc.c renamed from mm/page_alloc.c~migrate_pages-add-success-return +3 -1
@@ -8341,6 +8341,7 @@ static int __alloc_contig_migrate_range(unsigned long pfn = start;unsigned int tries =0 ;int ret =0 ; +unsigned int nr_succeeded =0 ;migrate_prep ();
@@ -8368,7 +8369,8 @@ static int __alloc_contig_migrate_range( cc->nr_migratepages -= nr_reclaimed; ret =migrate_pages (&cc->migratepages, alloc_migrate_target, -NULL ,0 , cc->mode,MR_CONTIG_RANGE ); +NULL ,0 , cc->mode,MR_CONTIG_RANGE , + &nr_succeeded); }if (ret <0 ) {putback_movable_pages (&cc->migratepages);
_
Re: [RFC][PATCH 4/9] mm/migrate: make migrate_pages() return nr_succeeded
Quoted 25 lines · Dave Hansen
diff -puN
mm/migrate.c~migrate_pages-add-success-returnmm/migrate.cmm/migrate.c renamed from mm/migrate.c~migrate_pages-add-success-return +2
-2@@ -1432,6 +1432,7 @@ out:* @mode: The migration mode that specifies the constraints for * page migration, if any. * @reason: The reason for page migration. +* @nr_succeeded: The number of pages migrated successfully. * * The function returns after 10 attempts or if no pages are movable any more * because the list has become empty or no retryable pages exist any more. @@ -1442,11 +1443,10 @@ out:*/ int migrate_pages (struct list_head *from,new_page_t get_new_page,free_page_t put_new_page,unsigned long private, -enum migrate_mode mode,int reason) +enum migrate_mode mode,int reason,unsigned int *nr_succeeded) {int retry =1 ;int nr_failed =0 ; -int nr_succeeded =0 ;int pass =0 ;struct page *page;struct page *page2;
mm/migrate.c retry: · line 1503
Quoted 5 lines
mm/migrate.c renamed from mm/migrate.c~migrate_pages-add-success-return +1
-1@@ -1500,7 +1500,7 @@ retry: retry++;break ;case MIGRATEPAGE_SUCCESS : - nr_succeeded++; + (*nr_succeeded)++;
I think now we should consider THP in counting now. Because later nr_succeeded will be used to counting the number of reclaimed pages, and THP is respected for that.
Best Regards,
Huang, Ying
Quoted 18 lines
mm/migrate.c renamed from mm/migrate.c~migrate_pages-add-success-return +3
-3break ;default :/* @@ -1517,11 +1517,11 @@ retry: nr_failed += retry; rc = nr_failed; out: -if (nr_succeeded) -count_vm_events (PGMIGRATE_SUCCESS , nr_succeeded); +if (*nr_succeeded) +count_vm_events (PGMIGRATE_SUCCESS , *nr_succeeded);if (nr_failed)count_vm_events (PGMIGRATE_FAIL , nr_failed); -trace_mm_migrate_pages (nr_succeeded, nr_failed, mode, reason); +trace_mm_migrate_pages (*nr_succeeded, nr_failed, mode, reason);if (!swapwrite) current->flags &= ~PF_SWAPWRITE ;
RFC PATCH v3 5/9 mm/migrate: demote pages during reclaim
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
This is mostly derived from a patch from Yang Shi:
https://lore.kernel.org/linux-mm/[email protected]/
Add code to the reclaim path (shrink_page_list()) to "demote" data to another NUMA node instead of discarding the data. This always avoids the cost of I/O needed to read the page back in and sometimes avoids the writeout cost when the pagee is dirty.
Note: This just adds the start of infratructure for migration. It is actually disabled next to the FIXME in migrate_demote_page_ok().
---
b/include/linux/migrate.h | 2 b/mm/vmscan.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+)
include/linux/migrate.h renamed from include/linux/migrate.h~demote-with-migrate_pages +2 -0
@@ -25,6 +25,7 @@ enum migrate_reason {MR_MEMPOLICY_MBIND ,MR_NUMA_MISPLACED ,MR_CONTIG_RANGE , +MR_DEMOTION , MR_TYPES };
@@ -210,6 +211,7 @@ struct migrate_vma {int migrate_vma_setup (struct migrate_vma *args);void migrate_vma_pages (struct migrate_vma *migrate);void migrate_vma_finalize (struct migrate_vma *migrate); +int next_demotion_node (int node);#endif /* CONFIG_MIGRATION */
mm/vmscan.c renamed from mm/vmscan.c~demote-with-migrate_pages +93 -0
129 lines not on this page · raw message
_
Re: [RFC][PATCH 5/9] mm/migrate: demote pages during reclaim
Hi, Dave,
Dave Hansen <[email protected]> writes:
[snip]
Quoted 138 lines
diff -puN
mm/vmscan.c~demote-with-migrate_pagesmm/vmscan.cmm/vmscan.c renamed from mm/vmscan.c~demote-with-migrate_pages +93
-0@@ -43,6 +43,7 @@#include <linux/kthread.h> #include <linux/freezer.h> #include <linux/memcontrol.h> +#include <linux/migrate.h> #include <linux/delayacct.h> #include <linux/sysctl.h> #include <linux/oom.h> @@ -1040,6 +1041,24 @@ static enum page_references page_check_rreturn PAGEREF_RECLAIM ; } +bool migrate_demote_page_ok (struct page *page,struct scan_control *sc) +{ +int next_nid =next_demotion_node (page_to_nid (page)); + +VM_BUG_ON_PAGE (!PageLocked (page), page); +VM_BUG_ON_PAGE (PageHuge (page), page); +VM_BUG_ON_PAGE (PageLRU (page), page); + +if (next_nid ==NUMA_NO_NODE ) +return false; +if (PageTransHuge (page) && !thp_migration_supported ()) +return false; + +// FIXME: actually enable this later in the series +return false; +} + +/* Check if a page is dirty or under writeback */ static void page_check_dirty_writeback (struct page *page,bool *dirty,bool *writeback)@@ -1070,6 +1089,66 @@ static void page_check_dirty_writeback(s mapping->a_ops->is_dirty_writeback (page, dirty, writeback); } +static struct page *alloc_demote_page (struct page *page,unsigned long node) +{ +/* +* Try to fail quickly if memory on the target node is not +* available. Leaving out __GFP_IO and __GFP_FS helps with +* this. If the desintation node is full, we want kswapd to +* run there so that its pages will get reclaimed and future +* migration attempts may succeed. +*/ +gfp_t flags = (__GFP_HIGHMEM | __GFP_MOVABLE | __GFP_NORETRY | + __GFP_NOMEMALLOC | __GFP_NOWARN | __GFP_THISNODE | + __GFP_KSWAPD_RECLAIM); +/* HugeTLB pages should not be on the LRU */ +WARN_ON_ONCE (PageHuge (page)); + +if (PageTransHuge (page)) { +struct page *thp; + + flags |= __GFP_COMP; + + thp =alloc_pages_node (node, flags,HPAGE_PMD_ORDER ); +if (!thp) +return NULL ; +prep_transhuge_page (thp); +return thp; + } + +return __alloc_pages_node (node, flags,0 ); +} + +/* +* Take pages on @demote_list and attempt to demote them to +* another node. Pages which are not demoted are added to +* @ret_pages. +*/ +static unsigned int demote_page_list (struct list_head *ret_list, +struct list_head *demote_pages, +struct pglist_data *pgdat, +struct scan_control *sc) +{ +int target_nid =next_demotion_node (pgdat->node_id); +unsigned int nr_succeeded =0 ; +int err; + +if (list_empty (demote_pages)) +return 0 ; + +/* Demotion ignores all cpuset and mempolicy settings */ + err =migrate_pages (demote_pages, alloc_demote_page,NULL , + target_nid,MIGRATE_ASYNC ,MR_DEMOTION , + &nr_succeeded); + +if (err) { +putback_movable_pages (demote_pages); +list_splice (ret_list, demote_pages); + } + +return nr_succeeded; +} +/* * shrink_page_list() returns the number of reclaimed pages */ @@ -1082,6 +1161,7 @@ static unsigned int shrink_page_list(str {LIST_HEAD (ret_pages);LIST_HEAD (free_pages); +LIST_HEAD (demote_pages);unsigned int nr_reclaimed =0 ;unsigned int pgactivate =0 ;@@ -1237,6 +1317,16 @@ static unsigned int shrink_page_list(str }/* +* Before reclaiming the page, try to relocate +* its contents to another node. +*/ +if (migrate_demote_page_ok (page, sc)) { +list_add (&page->lru, &demote_pages); +unlock_page (page); +continue ; + } + +/* * Anonymous process memory has backing store? * Try to allocate it some swap space here. * Lazyfree page could be freed directly @@ -1484,6 +1574,9 @@ keep:VM_BUG_ON_PAGE (PageLRU (page) ||PageUnevictable (page), page); } +/* Migrate pages selected for demotion */ + nr_reclaimed +=demote_page_list (&ret_pages, &demote_pages, pgdat, sc); + pgactivate = stat->nr_activate[0 ] + stat->nr_activate[1 ];mem_cgroup_uncharge_list (&free_pages);
Quoted 1 line
_
Generally, it's good to batch the page migration. But one side effect is that, if the pages are failed to be migrated, they will be placed back to the LRU list instead of falling back to be reclaimed really. This may cause some issue in some situation. For example, if there's no enough space in the PMEM (slow) node, so the page migration fails, OOM may be triggered, because the direct reclaiming on the DRAM (fast) node may make no progress, while it can reclaim some pages really before.
Best Regards,
Huang, Ying
Re: [RFC][PATCH 5/9] mm/migrate: demote pages during reclaim
Quoted 5 lines · Ying
Quoted 5 lines
mm/vmscan.c renamed from mm/vmscan.c~demote-with-migrate_pages +3
-0+/* Migrate pages selected for demotion */ + nr_reclaimed +=demote_page_list (&ret_pages, &demote_pages, pgdat, sc); + pgactivate = stat->nr_activate[0 ] + stat->nr_activate[1 ];
Quoted 9 lines
Quoted 2 lines
mem_cgroup_uncharge_list(&free_pages);
_Generally, it's good to batch the page migration. But one side effect is that, if the pages are failed to be migrated, they will be placed back to the LRU list instead of falling back to be reclaimed really. This may cause some issue in some situation. For example, if there's no enough space in the PMEM (slow) node, so the page migration fails, OOM may be triggered, because the direct reclaiming on the DRAM (fast) node may make no progress, while it can reclaim some pages really before.
Yes, agreed.
There are a couple of ways we could fix this. Instead of splicing 'demote_pages' back into 'ret_pages', we could try to get them back on 'page_list' and goto the beginning on shrink_page_list(). This will probably yield the best behavior, but might be a bit ugly.
We could also add a field to 'struct scan_control' and just stop trying to migrate after it has failed one or more times. The trick will be picking a threshold that doesn't mess with either the normal reclaim rate or the migration rate.
This is on my list to fix up next.
Re: [RFC][PATCH 5/9] mm/migrate: demote pages during reclaim
Quoted 17 lines · Dave Hansen
Quoted 7 lines · Ying
Quoted 7 lines
mm/vmscan.c renamed from mm/vmscan.c~demote-with-migrate_pages +3
-0+/* Migrate pages selected for demotion */ + nr_reclaimed +=demote_page_list (&ret_pages, &demote_pages, pgdat, sc); + pgactivate = stat->nr_activate[0 ] + stat->nr_activate[1 ];mem_cgroup_uncharge_list (&free_pages);_
Quoted 7 lines
Generally, it's good to batch the page migration. But one side effect is that, if the pages are failed to be migrated, they will be placed back to the LRU list instead of falling back to be reclaimed really. This may cause some issue in some situation. For example, if there's no enough space in the PMEM (slow) node, so the page migration fails, OOM may be triggered, because the direct reclaiming on the DRAM (fast) node may make no progress, while it can reclaim some pages really before.
Quoted 1 line
Yes, agreed.
Kind of. But I think that should be transient and very rare. The kswapd on pmem nodes will be waken up to drop pages when we try to allocate migration target pages. It should be very rare that there is not reclaimable page on pmem nodes.
Quoted 10 lines
There are a couple of ways we could fix this. Instead of splicing '
demote_pages' back into 'ret_pages', we could try to get them back on 'page_list' and goto the beginning onshrink_page_list(). This will probably yield the best behavior, but might be a bit ugly.We could also add a field to 'struct
scan_control' and just stop trying to migrate after it has failed one or more times. The trick will be picking a threshold that doesn't mess with either the normal reclaim rate or the migration rate.
In my patchset I implemented a fallback mechanism via adding a new PGDAT_CONTENDED node flag. Please check this out:
https://patchwork.kernel.org/patch/10993839/.
Basically the PGDAT_CONTENDED flag will be set once migrate_pages() return -ENOMEM which indicates the target pmem node is under memory pressure, then it would fallback to regular reclaim path. The flag would be cleared by clear_pgdat_congested() once the pmem node memory pressure is gone.
We already use node flags to indicate the state of node in reclaim code, i.e. PGDAT_WRITEBACK, PGDAT_DIRTY, etc. So, adding a new flag sounds more straightforward to me IMHO.
Quoted 3 lines
This is on my list to fix up next.
Re: [RFC][PATCH 5/9] mm/migrate: demote pages during reclaim
Quoted 40 lines · Yang Shi
Quoted 17 lines · Dave Hansen
Quoted 7 lines · Ying
Quoted 7 lines
mm/vmscan.c renamed from mm/vmscan.c~demote-with-migrate_pages +3
-0+/* Migrate pages selected for demotion */ + nr_reclaimed +=demote_page_list (&ret_pages, &demote_pages, pgdat, sc); + pgactivate = stat->nr_activate[0 ] + stat->nr_activate[1 ];mem_cgroup_uncharge_list (&free_pages);_
Quoted 7 lines
Generally, it's good to batch the page migration. But one side effect is that, if the pages are failed to be migrated, they will be placed back to the LRU list instead of falling back to be reclaimed really. This may cause some issue in some situation. For example, if there's no enough space in the PMEM (slow) node, so the page migration fails, OOM may be triggered, because the direct reclaiming on the DRAM (fast) node may make no progress, while it can reclaim some pages really before.
Quoted 1 line
Yes, agreed.
Kind of. But I think that should be transient and very rare. The kswapd on pmem nodes will be waken up to drop pages when we try to allocate migration target pages. It should be very rare that there is not reclaimable page on pmem nodes.
Quoted 10 lines
There are a couple of ways we could fix this. Instead of splicing '
demote_pages' back into 'ret_pages', we could try to get them back on 'page_list' and goto the beginning onshrink_page_list(). This will probably yield the best behavior, but might be a bit ugly.We could also add a field to 'struct
scan_control' and just stop trying to migrate after it has failed one or more times. The trick will be picking a threshold that doesn't mess with either the normal reclaim rate or the migration rate.In my patchset I implemented a fallback mechanism via adding a new
PGDAT_CONTENDEDnode flag. Please check this out:
https://patchwork.kernel.org/patch/10993839/.
Quoted 5 lines
Basically the
PGDAT_CONTENDEDflag will be set oncemigrate_pages()return-ENOMEMwhich indicates the target pmem node is under memory pressure, then it would fallback to regular reclaim path. The flag would be cleared byclear_pgdat_congested()once the pmem node memory pressure is gone.
There may be some races between the flag set and clear. For example,
- try to migrate some pages from DRAM node to PMEM node
- no enough free pages on the PMEM node, so wakeup kswapd
- kswapd on PMEM node reclaimed some page and try to clear PGDAT_CONTENDED on DRAM node
- set PGDAT_CONTENDED on DRAM node
This may be resolvable. But I still prefer to fallback to real page reclaiming directly for the pages failed to be migrated. That looks more robust.
Best Regards,
Huang, Ying
Quoted 7 lines
We already use node flags to indicate the state of node in reclaim code, i.e.
PGDAT_WRITEBACK,PGDAT_DIRTY, etc. So, adding a new flag sounds more straightforward to me IMHO.Quoted 3 lines
This is on my list to fix up next.
Re: [RFC][PATCH 5/9] mm/migrate: demote pages during reclaim
Quoted 51 lines · Ying
Quoted 40 lines · Yang Shi
Quoted 17 lines · Dave Hansen
Quoted 7 lines · Ying
Quoted 7 lines
mm/vmscan.c renamed from mm/vmscan.c~demote-with-migrate_pages +3
-0+/* Migrate pages selected for demotion */ + nr_reclaimed +=demote_page_list (&ret_pages, &demote_pages, pgdat, sc); + pgactivate = stat->nr_activate[0 ] + stat->nr_activate[1 ];mem_cgroup_uncharge_list (&free_pages);_
Quoted 7 lines
Generally, it's good to batch the page migration. But one side effect is that, if the pages are failed to be migrated, they will be placed back to the LRU list instead of falling back to be reclaimed really. This may cause some issue in some situation. For example, if there's no enough space in the PMEM (slow) node, so the page migration fails, OOM may be triggered, because the direct reclaiming on the DRAM (fast) node may make no progress, while it can reclaim some pages really before.
Quoted 1 line
Yes, agreed.
Kind of. But I think that should be transient and very rare. The kswapd on pmem nodes will be waken up to drop pages when we try to allocate migration target pages. It should be very rare that there is not reclaimable page on pmem nodes.
Quoted 10 lines
There are a couple of ways we could fix this. Instead of splicing '
demote_pages' back into 'ret_pages', we could try to get them back on 'page_list' and goto the beginning onshrink_page_list(). This will probably yield the best behavior, but might be a bit ugly.We could also add a field to 'struct
scan_control' and just stop trying to migrate after it has failed one or more times. The trick will be picking a threshold that doesn't mess with either the normal reclaim rate or the migration rate.In my patchset I implemented a fallback mechanism via adding a new
PGDAT_CONTENDEDnode flag. Please check this out:
https://patchwork.kernel.org/patch/10993839/.Quoted 5 lines
Basically the
PGDAT_CONTENDEDflag will be set oncemigrate_pages()return-ENOMEMwhich indicates the target pmem node is under memory pressure, then it would fallback to regular reclaim path. The flag would be cleared byclear_pgdat_congested()once the pmem node memory pressure is gone.There may be some races between the flag set and clear. For example,
Quoted 8 lines
- try to migrate some pages from DRAM node to PMEM node
- no enough free pages on the PMEM node, so wakeup kswapd
- kswapd on PMEM node reclaimed some page and try to clear
PGDAT_CONTENDEDon DRAM node- set
PGDAT_CONTENDEDon DRAM node
Yes, the race is true. Someone else may set PGDAT_CONTENDED, but pmem node's kswapd already went to sleep, so the flag might be not be able to be cleared for a while.
I think this can be solved easily. We can just move the flag set to kswapd. Once kswapd is waken up we know there is kind of memory pressure on that node, then set the flag, clear the flag when kswapd goes to sleep. kswapd is single threaded and just set/clear its own node's flag, so there should be no race if I don't miss something.
Quoted 15 lines
This may be resolvable. But I still prefer to fallback to real page reclaiming directly for the pages failed to be migrated. That looks more robust.
Best Regards,
Huang, YingQuoted 7 lines
We already use node flags to indicate the state of node in reclaim code, i.e.
PGDAT_WRITEBACK,PGDAT_DIRTY, etc. So, adding a new flag sounds more straightforward to me IMHO.Quoted 3 lines
This is on my list to fix up next.
Re: [RFC][PATCH 5/9] mm/migrate: demote pages during reclaim
In reply to Dave HansenQuoted 59 lines · Dave Hansen
From: Dave Hansen <[email protected]>
This is mostly derived from a patch from Yang Shi:
https://lore.kernel.org/linux-mm/[email protected]/Add code to the reclaim path (
shrink_page_list()) to "demote" data to another NUMA node instead of discarding the data. This always avoids the cost of I/O needed to read the page back in and sometimes avoids the writeout cost when the pagee is dirty.Note: This just adds the start of infratructure for migration. It is actually disabled next to the FIXME in
migrate_demote_page_ok().Signed-off-by Dave Hansen <[email protected]>Cc Yang Shi <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/include/linux/migrate.h | 2 b/mm/vmscan.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+)diff -puN
include/linux/migrate.h~demote-with-migrate_pagesinclude/linux/migrate.hinclude/linux/migrate.h 2020-08-18 11:36:52.923583179 -0700 renamed from include/linux/migrate.h~demote-with-migrate_pages 2020-08-18 11:36:52.916583179 -0700 +2
-0@@ -25,6 +25,7 @@ enum migrate_reason { MR_MEMPOLICY_MBIND, MR_NUMA_MISPLACED, MR_CONTIG_RANGE, + MR_DEMOTION, MR_TYPES };@@ -210,6 +211,7 @@ struct migrate_vma { int migrate_vma_setup(struct migrate_vma *args); void migrate_vma_pages(struct migrate_vma *migrate); void migrate_vma_finalize(struct migrate_vma *migrate); +int next_demotion_node(int node); #endif /* CONFIG_MIGRATION */diff -puN
mm/vmscan.c~demote-with-migrate_pagesmm/vmscan.cmm/vmscan.c 2020-08-18 11:36:52.924583179 -0700 renamed from mm/vmscan.c~demote-with-migrate_pages 2020-08-18 11:36:52.919583179 -0700 +1
-0@@ -43,6 +43,7 @@ #include <linux/kthread.h> #include <linux/freezer.h> #include <linux/memcontrol.h> +#include <linux/migrate.h> #include <linux/delayacct.h> #include <linux/sysctl.h> #include <linux/oom.h>@@ -1040,6 +1041,24 @@ static enum page_references page_check_r return PAGEREF_RECLAIM;
mm/vmscan.c 2020-08-18 11:36:52.924583179 -0700 static enum page_references page_check_r · line 1051
Quoted 10 lines
mm/vmscan.c 2020-08-18 11:36:52.924583179 -0700 renamed from mm/vmscan.c~demote-with-migrate_pages 2020-08-18 11:36:52.919583179 -0700 +8
-0} +bool migrate_demote_page_ok(struct page *page, struct scan_control *sc) +{ + int next_nid = next_demotion_node(page_to_nid(page)); + + VM_BUG_ON_PAGE(!PageLocked(page), page); + VM_BUG_ON_PAGE(PageHuge(page), page); + VM_BUG_ON_PAGE(PageLRU(page), page); +
I think we'd better rule out MADV_FREE pages since it doesn't make too much sense to migrate MADV_FREE pages, they can be just discarded.
Quoted 116 lines
mm/vmscan.c 2020-08-18 11:36:52.924583179 -0700 renamed from mm/vmscan.c~demote-with-migrate_pages 2020-08-18 11:36:52.919583179 -0700 +84
-0+ if (next_nid == NUMA_NO_NODE) + return false; + if (PageTransHuge(page) && !thp_migration_supported()) + return false; + + // FIXME: actually enable this later in the series + return false; +} + + /* Check if a page is dirty or under writeback */ static void page_check_dirty_writeback(struct page *page, bool *dirty, bool *writeback)@@ -1070,6 +1089,66 @@ static void page_check_dirty_writeback(s mapping->a_ops->is_dirty_writeback(page, dirty, writeback); } +static struct page *alloc_demote_page(struct page *page, unsigned long node) +{ + /* + * Try to fail quickly if memory on the target node is not + * available. Leaving out __GFP_IO and __GFP_FS helps with + * this. If the desintation node is full, we want kswapd to + * run there so that its pages will get reclaimed and future + * migration attempts may succeed. + */ + gfp_t flags = (__GFP_HIGHMEM | __GFP_MOVABLE | __GFP_NORETRY | + __GFP_NOMEMALLOC | __GFP_NOWARN | __GFP_THISNODE | + __GFP_KSWAPD_RECLAIM); + /* HugeTLB pages should not be on the LRU */ + WARN_ON_ONCE(PageHuge(page)); + + if (PageTransHuge(page)) { + struct page *thp; + + flags |= __GFP_COMP; + + thp = alloc_pages_node(node, flags, HPAGE_PMD_ORDER); + if (!thp) + return NULL; + prep_transhuge_page(thp); + return thp; + } + + return __alloc_pages_node(node, flags, 0); +} + +/* + * Take pages on @demote_list and attempt to demote them to + * another node. Pages which are not demoted are added to + * @ret_pages. + */ +static unsigned int demote_page_list(struct list_head *ret_list, + struct list_head *demote_pages, + struct pglist_data *pgdat, + struct scan_control *sc) +{ + int target_nid = next_demotion_node(pgdat->node_id); + unsigned int nr_succeeded = 0; + int err; + + if (list_empty(demote_pages)) + return 0; + + /* Demotion ignores all cpuset and mempolicy settings */ + err = migrate_pages(demote_pages, alloc_demote_page, NULL, + target_nid, MIGRATE_ASYNC, MR_DEMOTION, + &nr_succeeded); + + if (err) { + putback_movable_pages(demote_pages); + list_splice(ret_list, demote_pages); + } + + return nr_succeeded; +} + /* * shrink_page_list() returns the number of reclaimed pages */@@ -1082,6 +1161,7 @@ static unsigned int shrink_page_list(str { LIST_HEAD(ret_pages); LIST_HEAD(free_pages); + LIST_HEAD(demote_pages); unsigned int nr_reclaimed = 0; unsigned int pgactivate = 0;@@ -1237,6 +1317,16 @@ static unsigned int shrink_page_list(str } /* + * Before reclaiming the page, try to relocate + * its contents to another node. + */ + if (migrate_demote_page_ok(page, sc)) { + list_add(&page->lru, &demote_pages); + unlock_page(page); + continue; + } + + /* * Anonymous process memory has backing store? * Try to allocate it some swap space here. * Lazyfree page could be freed directly@@ -1484,6 +1574,9 @@ keep: VM_BUG_ON_PAGE(PageLRU(page) || PageUnevictable(page), page); } + /* Migrate pages selected for demotion */ + nr_reclaimed += demote_page_list(&ret_pages, &demote_pages, pgdat, sc); + pgactivate = stat->nr_activate[0] + stat->nr_activate[1]; mem_cgroup_uncharge_list(&free_pages);_
RFC PATCH v3 6/9 mm/vmscan: add page demotion counter
In reply to Dave HansenFrom: Yang Shi <[email protected]>
Account the number of demoted pages into reclaim_state->nr_demoted.
Add pgdemote_kswapd and pgdemote_direct VM counters showed in /proc/vmstat.
[ daveh:
- __count_vm_events() a bit, and made them look at the THP size directly rather than getting data from migrate_pages()
]
---
b/include/linux/vm_event_item.h | 2 ++ b/mm/vmscan.c | 6 ++++++ b/mm/vmstat.c | 2 ++ 3 files changed, 10 insertions(+)
include/linux/vm_event_item.h renamed from include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counter +2 -0
@@ -32,6 +32,8 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSPGREFILL ,PGSTEAL_KSWAPD ,PGSTEAL_DIRECT , +PGDEMOTE_KSWAPD , +PGDEMOTE_DIRECT ,PGSCAN_KSWAPD ,PGSCAN_DIRECT , PGSCAN_DIRECT_THROTTLE,
mm/vmscan.c renamed from mm/vmscan.c~mm-vmscan-add-page-demotion-counter +6 -0
@@ -147,6 +147,7 @@ struct scan_control {unsigned int immediate;unsigned int file_taken;unsigned int taken; +unsigned int demoted; } nr;/* for recording the reclaimed slab by now */
@@ -1146,6 +1147,11 @@ static unsigned int demote_page_list(strlist_splice (ret_list, demote_pages); } +if (current_is_kswapd ()) +__count_vm_events (PGDEMOTE_KSWAPD , nr_succeeded); +else +__count_vm_events (PGDEMOTE_DIRECT , nr_succeeded); +return nr_succeeded; }
mm/vmstat.c renamed from mm/vmstat.c~mm-vmscan-add-page-demotion-counter +2 -0
@@ -1200,6 +1200,8 @@ const char * const vmstat_text[] = {"pgrefill" ,"pgsteal_kswapd" ,"pgsteal_direct" , +"pgdemote_kswapd" , +"pgdemote_direct" ,"pgscan_kswapd" ,"pgscan_direct" , "pgscan_direct_throttle",
_
Re: [RFC][PATCH 6/9] mm/vmscan: add page demotion counter
Quoted 6 lines · Dave Hansen
From: Yang Shi <[email protected]>
Account the number of demoted pages into
reclaim_state->nr_demoted.
Quoted 7 lines
Add
pgdemote_kswapdandpgdemote_directVM counters showed in/proc/vmstat.[ daveh:
-__count_vm_events()a bit, and made them look at the THP size directly rather than getting data frommigrate_pages()
]
This reminds me that we'd better to make migrate_pages() return the number of base pages. Now both nr_failed and nr_succeeded is inc'ed regardless of whether it is THP.
A quick look finds the callers of migrate_pages() seem not care about the exact returned number, they just care if it is positive or not.
And reclaim code depends on correct nr_succeeded to avoid over reclaim.
I will come up with a patch to post to the mailing list.
Quoted 61 lines
Signed-off-by Yang Shi <[email protected]>Signed-off-by Dave Hansen <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/include/linux/vm_event_item.h | 2 ++ b/mm/vmscan.c | 6 ++++++ b/mm/vmstat.c | 2 ++ 3 files changed, 10 insertions(+)diff -puN
include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counterinclude/linux/vm_event_item.hinclude/linux/vm_event_item.h 2020-08-18 11:36:54.070583176 -0700 renamed from include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.062583176 -0700 +2
-0@@ -32,6 +32,8 @@ enum vm_event_item { PGPGIN, PGPGOUT, PS PGREFILL, PGSTEAL_KSWAPD, PGSTEAL_DIRECT, + PGDEMOTE_KSWAPD, + PGDEMOTE_DIRECT, PGSCAN_KSWAPD, PGSCAN_DIRECT, PGSCAN_DIRECT_THROTTLE,diff -puN
mm/vmscan.c~mm-vmscan-add-page-demotion-countermm/vmscan.cmm/vmscan.c 2020-08-18 11:36:54.072583176 -0700 renamed from mm/vmscan.c~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.064583176 -0700 +6
-0@@ -147,6 +147,7 @@ struct scan_control { unsigned int immediate; unsigned int file_taken; unsigned int taken; + unsigned int demoted; } nr; /* for recording the reclaimed slab by now */@@ -1146,6 +1147,11 @@ static unsigned int demote_page_list(str list_splice(ret_list, demote_pages); } + if (current_is_kswapd()) + __count_vm_events(PGDEMOTE_KSWAPD, nr_succeeded); + else + __count_vm_events(PGDEMOTE_DIRECT, nr_succeeded); + return nr_succeeded; }diff -puN
mm/vmstat.c~mm-vmscan-add-page-demotion-countermm/vmstat.cmm/vmstat.c 2020-08-18 11:36:54.072583176 -0700 renamed from mm/vmstat.c~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.067583176 -0700 +2
-0@@ -1200,6 +1200,8 @@ const char * const vmstat_text[] = { "pgrefill", "pgsteal_kswapd", "pgsteal_direct", + "pgdemote_kswapd", + "pgdemote_direct", "pgscan_kswapd", "pgscan_direct", "pgscan_direct_throttle",_
Re: [RFC][PATCH 6/9] mm/vmscan: add page demotion counter
Quoted 21 lines · Yang Shi
Quoted 6 lines · Dave Hansen
From: Yang Shi <[email protected]>
Account the number of demoted pages into
reclaim_state->nr_demoted.Quoted 7 lines
Add
pgdemote_kswapdandpgdemote_directVM counters showed in/proc/vmstat.[ daveh:
-__count_vm_events()a bit, and made them look at the THP size directly rather than getting data frommigrate_pages()
]This reminds me that we'd better to make
migrate_pages()return the number of base pages. Now bothnr_failedandnr_succeededis inc'ed regardless of whether it is THP.
Quoted 6 lines
A quick look finds the callers of
migrate_pages()seem not care about the exact returned number, they just care if it is positive or not.And reclaim code depends on correct
nr_succeededto avoid over reclaim.I will come up with a patch to post to the mailing list.
I looks this problem has been fixed by commit 1a5bae25e3cf95c4e83a97f87a6b5280d9acbb22 ("mm/vmstat: add events for THP migration without split") in v5.9-rc1. With that commit, nr_succeeded will be bumped by 512 if a THP is migrated successfully (without split). So, we don't need worry about this anymore.
Quoted 62 lines
Quoted 62 lines
Signed-off-by Yang Shi <[email protected]>Signed-off-by Dave Hansen <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/include/linux/vm_event_item.h | 2 ++ b/mm/vmscan.c | 6 ++++++ b/mm/vmstat.c | 2 ++ 3 files changed, 10 insertions(+)diff -puN
include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counterinclude/linux/vm_event_item.hinclude/linux/vm_event_item.h 2020-08-18 11:36:54.070583176 -0700 renamed from include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.062583176 -0700 +2
-0@@ -32,6 +32,8 @@ enum vm_event_item { PGPGIN, PGPGOUT, PS PGREFILL, PGSTEAL_KSWAPD, PGSTEAL_DIRECT, + PGDEMOTE_KSWAPD, + PGDEMOTE_DIRECT, PGSCAN_KSWAPD, PGSCAN_DIRECT, PGSCAN_DIRECT_THROTTLE,diff -puN
mm/vmscan.c~mm-vmscan-add-page-demotion-countermm/vmscan.cmm/vmscan.c 2020-08-18 11:36:54.072583176 -0700 renamed from mm/vmscan.c~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.064583176 -0700 +6
-0@@ -147,6 +147,7 @@ struct scan_control { unsigned int immediate; unsigned int file_taken; unsigned int taken; + unsigned int demoted; } nr; /* for recording the reclaimed slab by now */@@ -1146,6 +1147,11 @@ static unsigned int demote_page_list(str list_splice(ret_list, demote_pages); } + if (current_is_kswapd()) + __count_vm_events(PGDEMOTE_KSWAPD, nr_succeeded); + else + __count_vm_events(PGDEMOTE_DIRECT, nr_succeeded); + return nr_succeeded; }diff -puN
mm/vmstat.c~mm-vmscan-add-page-demotion-countermm/vmstat.cmm/vmstat.c 2020-08-18 11:36:54.072583176 -0700 renamed from mm/vmstat.c~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.067583176 -0700 +2
-0@@ -1200,6 +1200,8 @@ const char * const vmstat_text[] = { "pgrefill", "pgsteal_kswapd", "pgsteal_direct", + "pgdemote_kswapd", + "pgdemote_direct", "pgscan_kswapd", "pgscan_direct", "pgscan_direct_throttle",_
Re: [RFC][PATCH 6/9] mm/vmscan: add page demotion counter
In reply to Dave HansenQuoted 8 lines · Dave Hansen
From: Yang Shi <[email protected]>
Account the number of demoted pages into
reclaim_state->nr_demoted.Add
pgdemote_kswapdandpgdemote_directVM counters showed in/proc/vmstat.
BTW we'd better add promotion counters as well. The NUMA balancing could promote pages to local nodes without any modification. We could argue it may be optimized for PMEM usecases, but it does work. And it makes the patchset more self-contained.
You could refer to:
https://lore.kernel.org/linux-mm/[email protected]/
Quoted 66 lines
[ daveh:
-__count_vm_events()a bit, and made them look at the THP size directly rather than getting data frommigrate_pages()
]Signed-off-by Yang Shi <[email protected]>Signed-off-by Dave Hansen <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/include/linux/vm_event_item.h | 2 ++ b/mm/vmscan.c | 6 ++++++ b/mm/vmstat.c | 2 ++ 3 files changed, 10 insertions(+)diff -puN
include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counterinclude/linux/vm_event_item.hinclude/linux/vm_event_item.h 2020-08-18 11:36:54.070583176 -0700 renamed from include/linux/vm_event_item.h~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.062583176 -0700 +2
-0@@ -32,6 +32,8 @@ enum vm_event_item { PGPGIN, PGPGOUT, PS PGREFILL, PGSTEAL_KSWAPD, PGSTEAL_DIRECT, + PGDEMOTE_KSWAPD, + PGDEMOTE_DIRECT, PGSCAN_KSWAPD, PGSCAN_DIRECT, PGSCAN_DIRECT_THROTTLE,diff -puN
mm/vmscan.c~mm-vmscan-add-page-demotion-countermm/vmscan.cmm/vmscan.c 2020-08-18 11:36:54.072583176 -0700 renamed from mm/vmscan.c~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.064583176 -0700 +6
-0@@ -147,6 +147,7 @@ struct scan_control { unsigned int immediate; unsigned int file_taken; unsigned int taken; + unsigned int demoted; } nr; /* for recording the reclaimed slab by now */@@ -1146,6 +1147,11 @@ static unsigned int demote_page_list(str list_splice(ret_list, demote_pages); } + if (current_is_kswapd()) + __count_vm_events(PGDEMOTE_KSWAPD, nr_succeeded); + else + __count_vm_events(PGDEMOTE_DIRECT, nr_succeeded); + return nr_succeeded; }diff -puN
mm/vmstat.c~mm-vmscan-add-page-demotion-countermm/vmstat.cmm/vmstat.c 2020-08-18 11:36:54.072583176 -0700 renamed from mm/vmstat.c~mm-vmscan-add-page-demotion-counter 2020-08-18 11:36:54.067583176 -0700 +2
-0@@ -1200,6 +1200,8 @@ const char * const vmstat_text[] = { "pgrefill", "pgsteal_kswapd", "pgsteal_direct", + "pgdemote_kswapd", + "pgdemote_direct", "pgscan_kswapd", "pgscan_direct", "pgscan_direct_throttle",_
RFC PATCH v3 7/9 mm/vmscan: Consider anonymous pages without swap
In reply to Dave HansenFrom: Keith Busch <[email protected]>
Age and reclaim anonymous pages if a migration path is available. The node has other recourses for inactive anonymous pages beyond swap,
#Signed-off-by: Keith Busch <[email protected]>
--
Changes from Dave 06/2020:
* rename reclaim_anon_pages()->can_reclaim_anon_pages()
Note: Keith's Intel SoB is commented out because he is no longer at Intel and his @intel.com mail will bouncee
---
b/include/linux/node.h | 9 +++++++++ b/mm/vmscan.c | 33 ++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 5 deletions(-)
include/linux/node.h renamed from include/linux/node.h~0009-mm-vmscan-Consider-anonymous-pages-without-swap +9 -0
@@ -180,4 +180,13 @@ static inline void register_hugetlbfs_wi#define to_node (device) container_of(device, struct node, dev) +#ifdef CONFIG_MIGRATION +extern int next_demotion_node (int node); +#else +static inline int next_demotion_node (int node) +{ +return NUMA_NO_NODE ; +} +#endif +#endif /* _LINUX_NODE_H_ */
mm/vmscan.c renamed from mm/vmscan.c~0009-mm-vmscan-Consider-anonymous-pages-without-swap +28 -5
@@ -295,6 +295,26 @@ static bool writeback_throttling_sane(st }#endif +static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, +int node_id) +{ +/* Always age anon pages when we have swap */ +if (memcg ==NULL ) { +if (get_nr_swap_pages () >0 ) +return true; + }else { +if (mem_cgroup_get_nr_swap_pages (memcg) >0 ) +return true; + } + +/* Also age anon pages if we can auto-migrate them */ +if (next_demotion_node (node_id) >=0 ) +return true; + +/* No way to reclaim anon pages */ +return false; +} +/* * This misses isolated pages which are not accounted for to save counters. * As the data only determines if reclaim or compaction continues, it is
@@ -306,7 +326,7 @@ unsigned long zone_reclaimable_pages(str nr =zone_page_state_snapshot (zone,NR_ZONE_INACTIVE_FILE ) +zone_page_state_snapshot (zone,NR_ZONE_ACTIVE_FILE ); -if (get_nr_swap_pages () >0 ) +if (can_reclaim_anon_pages (NULL ,zone_to_nid (zone))) nr +=zone_page_state_snapshot (zone,NR_ZONE_INACTIVE_ANON ) +zone_page_state_snapshot (zone,NR_ZONE_ACTIVE_ANON );
@@ -2338,6 +2358,7 @@ enum scan_balance {static void get_scan_count (struct lruvec *lruvec,struct scan_control *sc,unsigned long *nr) { +struct pglist_data *pgdat =lruvec_pgdat (lruvec);struct mem_cgroup *memcg =lruvec_memcg (lruvec);unsigned long anon_cost, file_cost, total_cost;int swappiness =mem_cgroup_swappiness (memcg);
@@ -2348,7 +2369,7 @@ static void get_scan_count(struct lruvecenum lru_list lru;/* If we have no swap space, do not bother scanning anon pages. */ -if (!sc->may_swap ||mem_cgroup_get_nr_swap_pages (memcg) <=0 ) { +if (!sc->may_swap || !can_reclaim_anon_pages (memcg, pgdat->node_id)) { scan_balance =SCAN_FILE ; goto out; }
@@ -2631,7 +2652,9 @@ static void shrink_lruvec(struct lruvec* Even if we did not try to evict anon pages at all, we want to * rebalance the anon lru active/inactive ratio. */ -if (total_swap_pages &&inactive_is_low (lruvec,LRU_INACTIVE_ANON )) +if (can_reclaim_anon_pages (lruvec_memcg (lruvec), +lruvec_pgdat (lruvec)->node_id) && +inactive_is_low (lruvec,LRU_INACTIVE_ANON ))shrink_active_list (SWAP_CLUSTER_MAX , lruvec, sc,LRU_ACTIVE_ANON ); }
@@ -2701,7 +2724,7 @@ static inline bool should_continue_recla*/ pages_for_compaction =compact_gap (sc->order); inactive_lru_pages =node_page_state (pgdat,NR_INACTIVE_FILE ); -if (get_nr_swap_pages () >0 ) +if (can_reclaim_anon_pages (NULL , pgdat->node_id)) inactive_lru_pages +=node_page_state (pgdat,NR_INACTIVE_ANON );return inactive_lru_pages > pages_for_compaction;
@@ -3462,7 +3485,7 @@ static void age_active_anon(struct pglisstruct mem_cgroup *memcg;struct lruvec *lruvec; -if (!total_swap_pages) +if (!can_reclaim_anon_pages (NULL , pgdat->node_id))return ; lruvec =mem_cgroup_lruvec (NULL , pgdat);
_
RFC PATCH v3 8/9 mm/vmscan: never demote for memcg reclaim
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
Global reclaim aims to reduce the amount of memory used on a given node or set of nodes. Migrating pages to another node serves this purpose.
memcg reclaim is different. Its goal is to reduce the total memory consumption of the entire memcg, across all nodes. Migration does not assist memcg reclaim because it just moves page contents between nodes rather than actually reducing memory consumption.
---
b/mm/vmscan.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-)
mm/vmscan.c renamed from mm/vmscan.c~never-demote-for-memcg-reclaim +25 -8
@@ -296,8 +296,11 @@ static bool writeback_throttling_sane(st#endif static inline bool can_reclaim_anon_pages (struct mem_cgroup *memcg, -int node_id) +int node_id, +struct scan_control *sc) { +bool in_cgroup_reclaim = false; +/* Always age anon pages when we have swap */ if (memcg ==NULL ) {if (get_nr_swap_pages () >0 )
@@ -307,8 +310,18 @@ static inline bool can_reclaim_anon_pagereturn true; } -/* Also age anon pages if we can auto-migrate them */ -if (next_demotion_node (node_id) >=0 ) +/* Can only be in memcg reclaim in paths with valid 'sc': */ +if (sc &&cgroup_reclaim (sc)) + in_cgroup_reclaim = true; + +/* +* Also age anon pages if we can auto-migrate them. +* +* Migrating a page does not reduce comsumption of a +* memcg so should not be performed when in memcg +* reclaim. +*/ +if (!in_cgroup_reclaim && (next_demotion_node (node_id) >=0 ))return true;/* No way to reclaim anon pages */
@@ -326,7 +339,7 @@ unsigned long zone_reclaimable_pages(str nr =zone_page_state_snapshot (zone,NR_ZONE_INACTIVE_FILE ) +zone_page_state_snapshot (zone,NR_ZONE_ACTIVE_FILE ); -if (can_reclaim_anon_pages (NULL ,zone_to_nid (zone))) +if (can_reclaim_anon_pages (NULL ,zone_to_nid (zone),NULL )) nr +=zone_page_state_snapshot (zone,NR_ZONE_INACTIVE_ANON ) +zone_page_state_snapshot (zone,NR_ZONE_ACTIVE_ANON );
@@ -1070,6 +1083,10 @@ bool migrate_demote_page_ok(struct pageVM_BUG_ON_PAGE (PageHuge (page), page);VM_BUG_ON_PAGE (PageLRU (page), page); +/* It is pointless to do demotion in memcg reclaim */ +if (cgroup_reclaim (sc)) +return false; +if (next_nid ==NUMA_NO_NODE )return false;if (PageTransHuge (page) && !thp_migration_supported ())
@@ -2369,7 +2386,7 @@ static void get_scan_count(struct lruvecenum lru_list lru;/* If we have no swap space, do not bother scanning anon pages. */ -if (!sc->may_swap || !can_reclaim_anon_pages (memcg, pgdat->node_id)) { +if (!sc->may_swap || !can_reclaim_anon_pages (memcg, pgdat->node_id, sc)) { scan_balance =SCAN_FILE ; goto out; }
@@ -2653,7 +2670,7 @@ static void shrink_lruvec(struct lruvec* rebalance the anon lru active/inactive ratio. */ if (can_reclaim_anon_pages (lruvec_memcg (lruvec), -lruvec_pgdat (lruvec)->node_id) && +lruvec_pgdat (lruvec)->node_id, sc) &&inactive_is_low (lruvec,LRU_INACTIVE_ANON ))shrink_active_list (SWAP_CLUSTER_MAX , lruvec, sc,LRU_ACTIVE_ANON );
@@ -2724,7 +2741,7 @@ static inline bool should_continue_recla*/ pages_for_compaction =compact_gap (sc->order); inactive_lru_pages =node_page_state (pgdat,NR_INACTIVE_FILE ); -if (can_reclaim_anon_pages (NULL , pgdat->node_id)) +if (can_reclaim_anon_pages (NULL , pgdat->node_id, sc)) inactive_lru_pages +=node_page_state (pgdat,NR_INACTIVE_ANON );return inactive_lru_pages > pages_for_compaction;
@@ -3485,7 +3502,7 @@ static void age_active_anon(struct pglisstruct mem_cgroup *memcg;struct lruvec *lruvec; -if (!can_reclaim_anon_pages (NULL , pgdat->node_id)) +if (!can_reclaim_anon_pages (NULL , pgdat->node_id, sc))return ; lruvec =mem_cgroup_lruvec (NULL , pgdat);
_
Re: [RFC][PATCH 8/9] mm/vmscan: never demote for memcg reclaim
Quoted 41 lines · Dave Hansen
From: Dave Hansen <[email protected]>
Global reclaim aims to reduce the amount of memory used on a given node or set of nodes. Migrating pages to another node serves this purpose.
memcg reclaim is different. Its goal is to reduce the total memory consumption of the entire memcg, across all nodes. Migration does not assist memcg reclaim because it just moves page contents between nodes rather than actually reducing memory consumption.
Signed-off-by Dave Hansen <[email protected]>Suggested-by Yang Shi <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>---
b/mm/vmscan.c | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-)diff -puN
mm/vmscan.c~never-demote-for-memcg-reclaimmm/vmscan.cmm/vmscan.c 2020-08-18 11:36:56.447583170 -0700 renamed from mm/vmscan.c~never-demote-for-memcg-reclaim 2020-08-18 11:36:56.442583170 -0700 +4
-1@@ -296,8 +296,11 @@ static bool writeback_throttling_sane(st #endif static inline bool can_reclaim_anon_pages(struct mem_cgroup *memcg, - int node_id) + int node_id, + struct scan_control *sc) { + bool in_cgroup_reclaim = false; + /* Always age anon pages when we have swap */ if (memcg == NULL) { if (get_nr_swap_pages() > 0)
mm/vmscan.c 2020-08-18 11:36:56.447583170 -0700 static inline bool can_reclaim_anon_page · line 313
Quoted 6 lines
mm/vmscan.c 2020-08-18 11:36:56.447583170 -0700 renamed from mm/vmscan.c~never-demote-for-memcg-reclaim 2020-08-18 11:36:56.442583170 -0700 +1
-2@@ -307,8 +310,18 @@ static inline bool can_reclaim_anon_page return true; } - /* Also age anon pages if we can auto-migrate them */ - if (next_demotion_node(node_id) >= 0) + /* Can only be in memcg reclaim in paths with valid 'sc': */
This comment looks not right. The global reclaim also has valid 'sc' but just with NULL target_mem_cgroup.
mm/vmscan.c 2020-08-18 11:36:56.447583170 -0700 static inline bool can_reclaim_anon_page · line 320
Quoted 7 lines
mm/vmscan.c 2020-08-18 11:36:56.447583170 -0700 renamed from mm/vmscan.c~never-demote-for-memcg-reclaim 2020-08-18 11:36:56.442583170 -0700 +7
-0+ if (sc && cgroup_reclaim(sc)) + in_cgroup_reclaim = true; + + /* + * Also age anon pages if we can auto-migrate them. + * + * Migrating a page does not reduce comsumption of a
s/comsuption/consumption
Quoted 64 lines
mm/vmscan.c 2020-08-18 11:36:56.447583170 -0700 renamed from mm/vmscan.c~never-demote-for-memcg-reclaim 2020-08-18 11:36:56.442583170 -0700 +13
-5+ * memcg so should not be performed when in memcg + * reclaim. + */ + if (!in_cgroup_reclaim && (next_demotion_node(node_id) >= 0)) return true; /* No way to reclaim anon pages */@@ -326,7 +339,7 @@ unsigned long zone_reclaimable_pages(str nr = zone_page_state_snapshot(zone, NR_ZONE_INACTIVE_FILE) + zone_page_state_snapshot(zone, NR_ZONE_ACTIVE_FILE); - if (can_reclaim_anon_pages(NULL, zone_to_nid(zone))) + if (can_reclaim_anon_pages(NULL, zone_to_nid(zone), NULL)) nr += zone_page_state_snapshot(zone, NR_ZONE_INACTIVE_ANON) + zone_page_state_snapshot(zone, NR_ZONE_ACTIVE_ANON);@@ -1070,6 +1083,10 @@ bool migrate_demote_page_ok(struct page VM_BUG_ON_PAGE(PageHuge(page), page); VM_BUG_ON_PAGE(PageLRU(page), page); + /* It is pointless to do demotion in memcg reclaim */ + if (cgroup_reclaim(sc)) + return false; + if (next_nid == NUMA_NO_NODE) return false; if (PageTransHuge(page) && !thp_migration_supported())@@ -2369,7 +2386,7 @@ static void get_scan_count(struct lruvec enum lru_list lru; /* If we have no swap space, do not bother scanning anon pages. */ - if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id)) { + if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) { scan_balance = SCAN_FILE; goto out; }@@ -2653,7 +2670,7 @@ static void shrink_lruvec(struct lruvec * rebalance the anon lru active/inactive ratio. */ if (can_reclaim_anon_pages(lruvec_memcg(lruvec), - lruvec_pgdat(lruvec)->node_id) && + lruvec_pgdat(lruvec)->node_id, sc) && inactive_is_low(lruvec, LRU_INACTIVE_ANON)) shrink_active_list(SWAP_CLUSTER_MAX, lruvec, sc, LRU_ACTIVE_ANON);@@ -2724,7 +2741,7 @@ static inline bool should_continue_recla */ pages_for_compaction = compact_gap(sc->order); inactive_lru_pages = node_page_state(pgdat, NR_INACTIVE_FILE); - if (can_reclaim_anon_pages(NULL, pgdat->node_id)) + if (can_reclaim_anon_pages(NULL, pgdat->node_id, sc)) inactive_lru_pages += node_page_state(pgdat, NR_INACTIVE_ANON); return inactive_lru_pages > pages_for_compaction;@@ -3485,7 +3502,7 @@ static void age_active_anon(struct pglis struct mem_cgroup *memcg; struct lruvec *lruvec; - if (!can_reclaim_anon_pages(NULL, pgdat->node_id)) + if (!can_reclaim_anon_pages(NULL, pgdat->node_id, sc)) return; lruvec = mem_cgroup_lruvec(NULL, pgdat);_
RFC PATCH v3 9/9 mm/migrate: new zone_reclaim_mode to enable reclaim migration
In reply to Dave HansenFrom: Dave Hansen <[email protected]>
Some method is obviously needed to enable reclaim-based migration.
Just like traditional autonuma, there will be some workloads that will benefit like workloads with more "static" configurations where hot pages stay hot and cold pages stay cold. If pages come and go from the hot and cold sets, the benefits of this approach will be more limited.
The benefits are truly workload-based and *not* hardware-based. We do not believe that there is a viable threshold where certain hardware configurations should have this mechanism enabled while others do not.
To be conservative, earlier work defaulted to disable reclaim- based migration and did not include a mechanism to enable it. This propses extending the existing "zone_reclaim_mode" (now now really node_reclaim_mode) as a method to enable it.
We are open to any alternative that allows end users to enable this mechanism or disable it it workload harm is detected (just like traditional autonuma).
---
b/Documentation/admin-guide/sysctl/vm.rst | 9 +++++++++ b/include/linux/swap.h | 3 ++- b/include/uapi/linux/mempolicy.h | 1 + b/mm/vmscan.c | 6 ++++-- 4 files changed, 16 insertions(+), 3 deletions(-)
Documentation/admin-guide/sysctl/vm.rst renamed from Documentation/admin-guide/sysctl/vm.rst~RECLAIM_MIGRATE +9 -0
@@ -954,6 +954,7 @@ This is value OR'ed together of 1 Zone reclaim on 2 Zone reclaim writes dirty pages out 4 Zone reclaim swaps pages +8 Zone reclaim migrates pages = =================================== zone_reclaim_mode is disabled by default. For file servers or workloads
@@ -978,3 +979,11 @@ of other processes running on other node Allowing regular swap effectively restricts allocations to the local node unless explicitly overridden by memory policies or cpuset configurations. + +Page migration during reclaim is intended for systems with tiered memory +configurations. These systems have multiple types of memory with varied +performance characteristics instead of plain NUMA systems where the same +kind of memory is found at varied distances. Allowing page migration +during reclaim enables these systems to migrate pages from fast tiers to +slow tiers when the fast tier is under pressure. This migration is +performed before swap.
include/linux/swap.h renamed from include/linux/swap.h~RECLAIM_MIGRATE +2 -1
@@ -387,7 +387,8 @@ extern int sysctl_min_slab_ratio;static inline bool node_reclaim_enabled (void ) {/* Is any node_reclaim_mode bit set? */ -return node_reclaim_mode & (RECLAIM_ZONE |RECLAIM_WRITE |RECLAIM_UNMAP ); +return node_reclaim_mode & (RECLAIM_ZONE |RECLAIM_WRITE | +RECLAIM_UNMAP |RECLAIM_MIGRATE ); }extern void check_move_unevictable_pages (struct pagevec *pvec);
include/uapi/linux/mempolicy.h renamed from include/uapi/linux/mempolicy.h~RECLAIM_MIGRATE +1 -0
@@ -69,5 +69,6 @@ enum {#define RECLAIM_ZONE (1<<0)/* Run shrink_inactive_list on the zone */ #define RECLAIM_WRITE (1<<1)/* Writeout pages during reclaim */ #define RECLAIM_UNMAP (1<<2)/* Unmap pages during reclaim */ +#define RECLAIM_MIGRATE (1<<3)/* Migrate to other nodes during reclaim */ #endif /* _UAPI_LINUX_MEMPOLICY_H */
mm/vmscan.c renamed from mm/vmscan.c~RECLAIM_MIGRATE +4 -2
@@ -1083,6 +1083,9 @@ bool migrate_demote_page_ok(struct pageVM_BUG_ON_PAGE (PageHuge (page), page);VM_BUG_ON_PAGE (PageLRU (page), page); +if (!(node_reclaim_mode &RECLAIM_MIGRATE )) +return false; +/* It is pointless to do demotion in memcg reclaim */ if (cgroup_reclaim (sc))return false;
@@ -1092,8 +1095,7 @@ bool migrate_demote_page_ok(struct pageif (PageTransHuge (page) && !thp_migration_supported ())return false; -// FIXME: actually enable this later in the series -return false; +return true; }
_
Quoted 5 lines · Dave Hansen
todo:
Changes since (https://lwn.net/Articles/824830/):
* Use higher-levelmigrate_pages()API approach from Yang Shi's earlier patches.
Thanks for incorporating in this. I believe this would be more efficient.
Quoted 41 lines
* made sure to actually check node_reclaim_mode's new bit
* disabled migration entirely before introducingRECLAIM_MIGRATE
* ReplaceGFP_NOWAITwith explicit__GFP_KSWAPD_RECLAIMand comment why we want that.
* Comment on effects of that keep multiple source nodes from sharing target nodesThe full series is also available here:
https://github.com/hansendc/linux/tree/automigrate-20200818--
We're starting to see systems with more and more kinds of memory such as Intel's implementation of persistent memory.
Let's say you have a system with some DRAM and some persistent memory. Today, once DRAM fills up, reclaim will start and some of the DRAM contents will be thrown out. Allocations will, at some point, start falling over to the slower persistent memory.
That has two nasty properties. First, the newer allocations can end up in the slower persistent memory. Second, reclaimed data in DRAM are just discarded even if there are gobs of space in persistent memory that could be used.
This set implements a solution to these problems. At the end of the reclaim process in
shrink_page_list()just before the last page refcount is dropped, the page is migrated to persistent memory instead of being dropped.While I've talked about a DRAM/PMEM pairing, this approach would function in any environment where memory tiers exist.
This is not perfect. It "strands" pages in slower memory and never brings them back to fast DRAM. Other things need to be built to promote hot pages back to DRAM.
This is also all based on an upstream mechanism that allows persistent memory to be onlined and used as if it were volatile:
Quoted 7 lines
http://lkml.kernel.org/r/[email protected]== Open Issues ==
* For cpusets and memory policies that restrict allocations to PMEM, is it OK to demote to PMEM? Do we need a cgroup- level API to opt-in or opt-out of these migrations?
I'm not sure if this would work or not, but AFAICT, it is unlikely. The nasty thing about cgroupv1 is you may end up having threads from the same process in different cgroups although it is rare.
My initial thought is to make cpuset process only (the threads in the same process must be in the same cpuset group), but it sounds not too feasible either since it may break some user configurations.
Quoted 2 lines
* Migration failures will result in pages being unreclaimable. Need to be able to fall back to normal reclaim.
That should be transient, shouldn't? The migration logic is supposed to wake up kswapd on pmem nodes, then the pages should become migratable in later retry.
Quoted 35 lines
Cc Yang Shi <[email protected]>Cc David Rientjes <[email protected]>Cc Huang Ying <[email protected]>Cc Dan Williams <[email protected]>--
Dave Hansen (5):
mm/numa: node demotion data structure and lookup mm/vmscan: Attempt to migrate page in lieu of discard mm/numa: automatically generate node migration order mm/vmscan: never demote for memcg reclaim mm/numa: new reclaim mode to enable reclaim-based migrationKeith Busch (2):
mm/migrate: Defer allocating new page until needed mm/vmscan: Consider anonymous pages without swapYang Shi (1):
mm/vmscan: add page demotion counterDocumentation/admin-guide/sysctl/vm.rst | 9 include/linux/migrate.h | 6 include/linux/node.h | 9 include/linux/vm_event_item.h | 2 include/trace/events/migrate.h | 3 mm/debug.c | 1 mm/internal.h | 1 mm/migrate.c | 400 ++++++++++++++++++++++++++------ mm/page_alloc.c | 2 mm/vmscan.c | 88 ++++++- mm/vmstat.c | 2 11 files changed, 439 insertions(+), 84 deletions(-)
Quoted 14 lines · Dave Hansen
Dave Hansen (5):
mm/numa: node demotion data structure and lookup mm/vmscan: Attempt to migrate page in lieu of discard mm/numa: automatically generate node migration order mm/vmscan: never demote for memcg reclaim mm/numa: new reclaim mode to enable reclaim-based migrationKeith Busch (2):
mm/migrate: Defer allocating new page until needed mm/vmscan: Consider anonymous pages without swapYang Shi (1):
mm/vmscan: add page demotion counter
Quoted 12 lines
Documentation/admin-guide/sysctl/vm.rst | 9 include/linux/migrate.h | 6 include/linux/node.h | 9 include/linux/vm_event_item.h | 2 include/trace/events/migrate.h | 3 mm/debug.c | 1 mm/internal.h | 1 mm/migrate.c | 400 ++++++++++++++++++++++++++------ mm/page_alloc.c | 2 mm/vmscan.c | 88 ++++++- mm/vmstat.c | 2 11 files changed, 439 insertions(+), 84 deletions(-)
The commit summary and diff stat appear to be from an earlier revision of the series. No big deal, just pointing that out. BTW, thank you for continuing to enhance this capability! :) I'd missed out on the earlier discussion from some bad filters that have should now be fixed (hopefully), so I've some catching up to do.