Refault distance checking for MGLRU
Subsystems (5)
v2 cover Refault distance checking for MGLRU
From: Kairui Song <[email protected]>
Hi, linux-mm
I noticed MGLRU not working very well on certain workflows, which is observed on some workloads with heavy memory stress.
After some debugging, I found this was related to refault distance detection, when the file page workingset size exceeds total memory, and the access distance (the left-shift time of a page before it gets activated or promoted, considering LRU starts from right) of file pages are larger than total memory. All file pages are stuck on the oldest generation and getting read-in then evicted permutably, few get activated and stay in memory.
This series tries to fix this problem by rework the refault distance based activation to better fit MGLRU, and also tries to use a unified algorithm for both MGLRU and Inactive/Active LRU, the performance almost doubled for the workloads that are not working well previously.
Patch 1/5 reworked the refault distance detection model for Inactive/Active LRU.
Patch 2/5 updated the comments.
Patch 3/5 and 4/5 are simplification and prepare.
Patch 4/4 applies the modified refault distance detection for MGLRU.
Following benchmark showed 5x improvement:
To simulate the workflow, I setup a 3-replicated mongodb cluster using docker, each in a standalone cgroup, set to use 5GB of wiretiger cache and 10g of oplog, on a 32G VM. The benchmark is done using
https://github.com/apavlo/py-tpcc.git, modified to run STOCK_LEVEL query only, for simulating slow query and get a stable result.
Before the patch (with 10G swap, the result won't change whether swap is on or not):
$ tpcc.py --config=mongodb.config mongodb --duration=900 --warehouses=500 --clients=30
==================================================================
Execution Results after 904 seconds
------------------------------------------------------------------
Executed Time (µs) Rate
STOCK_LEVEL 503 27150226136.4 0.02 txn/s
------------------------------------------------------------------
TOTAL 503 27150226136.4 0.02 txn/s$ cat /proc/vmstat | grep working workingset_nodes 53391 workingset_refault_anon 0 workingset_refault_file 23856735 workingset_activate_anon 0 workingset_activate_file 23845737 workingset_restore_anon 0 workingset_restore_file 18280692 workingset_nodereclaim 1024
$ free -m
total used free shared buff/cache available
Mem: 31837 6752 379 23 24706 24607
Swap: 10239 0 10239After the patch (with 10G swap on same disk, similar result using ZRAM):
$ tpcc.py --config=mongodb.config mongodb --duration=900 --warehouses=500 --clients=30
==================================================================
Execution Results after 903 seconds
------------------------------------------------------------------
Executed Time (µs) Rate
STOCK_LEVEL 2575 27094953498.8 0.10 txn/s
------------------------------------------------------------------
TOTAL 2575 27094953498.8 0.10 txn/s$ cat /proc/vmstat | grep working workingset_nodes 78249 workingset_refault_anon 10139 workingset_refault_file 23001863 workingset_activate_anon 7238 workingset_activate_file 6718032 workingset_restore_anon 7432 workingset_restore_file 6719406 workingset_nodereclaim 9747
$ free -m
total used free shared buff/cache available
Mem: 31837 7376 320 3 24140 24014
Swap: 10239 1662 8577The performance is 5x times better than before, and the idle anon pages now can get swapped out as expected. Testing with lower stress also shows a improvement.
I also checked the benchmark with memtier/memcached and fio and some other benchmarks, they looked OK so far, the results are in each commits.
Sending out as RFC, I'm still trying to do more test on it, since this changed a frequently used algorithm and not really sure if there is any performance regression, it should improvement the performance for file pages in general, since it saved some operations.
Update from V1:
- Removed the fls operations which previously used in patch 1 for protecting active pages by expontial ratio, simply compare with number of inactive pages seems good enough.
- Update some benchmarks results, test result that are basically identical as before are not updated.
Kairui Song (5):
workingset: simplify and use a more intuitive model
workingset: update comment in workingset.c
workingset: simplify lru_gen_test_recent
lru_gen: convert avg_total and avg_refaulted to atomic workingset, lru_gen: apply refault-distance based re-activation
include/linux/mmzone.h | 4 +- include/linux/swap.h | 2 - mm/swap.c | 1 - mm/vmscan.c | 18 +- mm/workingset.c | 411 +++++++++++++++++++++-------------------- 5 files changed, 221 insertions(+), 215 deletions(-)
Signature · 1 line
2.41.0
RFC PATCH v2 1/5 workingset: simplify and use a more intuitive model
From: Kairui Song <[email protected]>
This basically removed workingset_activation and reduced calls to workingset_age_nonresident.
The idea behind this change is a new way to calculate the refault distance and prepare for adapting refault distance based re-activation for multi-gen LRU.
Currently, refault distance re-activation is based on two assumptions:
1. Activation of an inactive page will left-shift LRU pages (considering LRU starts from right).
2. Eviction of an inactive page will left-shift LRU pages.
Assumption 2 is correct, but assumption 1 is not always true, an activated page could be anywhere in the LRU list (through mark_page_accessed), it only left-shift the pages on its right.
And besides, one page can get activate/deactivated for multiple times.
And multi-gen LRU doesn't fit with this model well, pages are getting aged and activated constantly as the generation sliding window slides.
So instead we introduce a simpler idea here: Just presume the evicted pages are still in memory, each has an eviction sequence like before. Let the nonresistence_age still be NA and get increased for each eviction, so we get a "Shadow LRU" here of one evicted page:
Let SP = ((NA's reading @ current) - (NA's reading @ eviction))
+-memory available to cache-+
| |
+-------------------------+===============+===========+
| * shadows O O O | INACTIVE | ACTIVE |
+-+-----------------------+===============+===========+
| |
+-----------------------+
| SP
fault page O -> Hole left by previously faulted in pages* -> The page corresponding to SP
It can be easily seen that SP stands for how far the current workflow could push a page out of available memory. Since all evicted page was once head of INACTIVE list, the page could have such an access distance:
SP + NR_INACTIVE
It *may* get re-activated before getting evicted again if:
SP + NR_INACTIVE < NR_INACTIVE + NR_ACTIVE
Which can be simplified to:
SP < NR_ACTIVE
Then the page is worth getting re-activated to start from ACTIVE part, since the access distance is shorter than the total memory to make it stay. The calculation is same as before, just dropped the assumption 1 above.
And since this is only an estimation, based on several hypotheses, and it could break the ability of LRU to distinguish a workingset out of caches, so throttle this by two factors:
1. Notice previously re-faulted in pages may leave "holes" on the shadow part of LRU, that part is left unhandled on purpose to decrease re-activate rate for pages that have a large SP value (the larger SP value a page has, the more likely it will be affected by such holes).
2. When the ACTIVE part of LRU is long enough, chanllaging ACTIVE pages by re-activating a one-time faulted previously INACTIVE page may not be a good idea, so throttle the re-activation when ACTIVE > INACTIVE by comparing with INACTIVE instead.
Combined all above, we have:
Upon refault, if any of following conditions is met, mark page as active:
- If ACTIVE LRU is low (NR_ACTIVE < NR_INACTIVE), check if:
SP < NR_ACTIVE
- If ACTIVE LRU is high (NR_ACTIVE >= NR_INACTIVE), check if:
SP < NR_INACTIVE
The code is almose same but simpler than before, since no longer need to do lruvec statistic update when activating a page. A few benchmarks showed a similar or better result. And when combined with multi-gen LRU (in later commits) it shows a measurable performance gain for some workloads.
Using memtier and fio test from commit ac35a4902374 but scaled down to fit in my test environment, and some other test results:
memtier test (with 16G ramdisk as swap and 2G memcg limit on an i7-9700):
memcached -u nobody -m 16384 -s /tmp/memcached.socket \
-a 0700 -t 12 -B binary &
memtier_benchmark -S /tmp/memcached.socket -P memcache_binary -n allkeys\
--key-minimum=1 --key-maximum=24000000 --key-pattern=P:P -c 1 \
-t 12 --ratio 1:0 --pipeline 8 -d 2000 -x 6
fio test 1 (with 16G ramdisk on 28G VM on an i7-9700):
fio -name=refault --numjobs=12 --directory=/mnt --size=1024m \
--buffered=1 --ioengine=io_uring --iodepth=128 \
--iodepth_batch_submit=32 --iodepth_batch_complete=32 \
--rw=randread --random_distribution=random --norandommap \
--time_based --ramp_time=5m --runtime=5m --group_reporting
fio test 2 (with 16G ramdisk on 28G VM on an i7-9700):
fio -name=mglru --numjobs=10 --directory=/mnt --size=1536m \
--buffered=1 --ioengine=io_uring --iodepth=128 \
--iodepth_batch_submit=32 --iodepth_batch_complete=32 \
--rw=randread --random_distribution=zipf:1.2 --norandommap \
--time_based --ramp_time=10m --runtime=5m --group_reporting mysql (using oltp_read_only from sysbench, with 12G of buffer pool in a 10G memcg):
sysbench /usr/share/sysbench/oltp_read_only.lua <auth and db params> \
--mysql-db=sb --tables=36 --table-size=2000000 --threads=12 --time=1800Before (Average of 6 test run):
fio: IOPS=5213.7k
fio2: IOPS=7315.3k
memcached: 49493.75 ops/s
mysql: 6237.45 tps
After (Average of 6 test run):
fio: IOPS=5230.5k
fio2: IOPS=7349.3k
memcached: 49912.79 ops/s
mysql: 6240.62 tps
--- include/linux/swap.h | 2 - mm/swap.c | 1 - mm/vmscan.c | 2 - mm/workingset.c | 215 +++++++++++++++++++++---------------------- 4 files changed, 106 insertions(+), 114 deletions(-)
include/linux/swap.h +0 -2
@@ -344,10 +344,8 @@ static inline swp_entry_t page_swap_entry(struct page *page)/* linux/mm/workingset.c */ bool workingset_test_recent (void *shadow,bool file,bool *workingset); -void workingset_age_nonresident (struct lruvec *lruvec,unsigned long nr_pages);void *workingset_eviction (struct folio *folio,struct mem_cgroup *target_memcg);void workingset_refault (struct folio *folio,void *shadow); -void workingset_activation (struct folio *folio);/* Only track the nodes of mappings with shadow entries */ void workingset_update_node (struct xa_node *node);
mm/swap.c +0 -1
@@ -482,7 +482,6 @@ void folio_mark_accessed(struct folio *folio)else __lru_cache_activate_folio (folio);folio_clear_referenced (folio); -workingset_activation (folio); }if (folio_test_idle (folio))folio_clear_idle (folio);
mm/vmscan.c +0 -2
@@ -2539,8 +2539,6 @@ static unsigned int move_folios_to_lru(struct lruvec *lruvec,lruvec_add_folio (lruvec, folio); nr_pages =folio_nr_pages (folio); nr_moved += nr_pages; -if (folio_test_active (folio)) -workingset_age_nonresident (lruvec, nr_pages); }/*
mm/workingset.c +106 -109
290 lines not on this page · raw message
Signature · 1 line
2.41.0
Johannes Weiner
maintainerRe: [RFC PATCH v2 1/5] workingset: simplify and use a more intuitive model
Hello Kairui,
Quoted 17 lines · Kairui Song
mm/workingset.c +12
-1@@ -226,8 +227,103 @@ static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, *workingsetp = workingset; } -#ifdef CONFIG_LRU_GEN +/* +* Get the distance reading at eviction time. +*/ +static inline unsigned long lru_eviction (struct lruvec *lruvec, +int bits,int bucket_order) +{ +unsigned long eviction =atomic_long_read (&lruvec->nonresident_age); + + eviction >>= bucket_order; + eviction &= ~0UL >> (BITS_PER_LONG - bits); + +return eviction;
mm/workingset.c static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, · line 251
Quoted 10 lines
mm/workingset.c +10
-0+} + +/* +* Calculate and test refault distance +*/ +static inline bool lru_refault (struct mem_cgroup *memcg, +struct lruvec *lruvec, +unsigned long eviction,bool file, +int bits,int bucket_order) +{
This patch changes the refault decision making at the same time as it moves the functions around in preparation for use by MGLRU. This makes it difficult to review the exact changes to the current algorithm.
Can you please separate those two components?
Step 1: Change the existing logic in its place to the new algorithm. Please also update the big comment on how everything works at the same time (IOW, merge patch #2 into this one).
Step 2: Add a new patch to refactor for MGLRU right before the patch that will use those functions. As far as I can see, that's the last patch in the series. So the refactor should be second-to-last.
Thanks
Re: [RFC PATCH v2 1/5] workingset: simplify and use a more intuitive model
Quoted 36 lines · Johannes Weiner <[email protected]> 于2023年9月13日周三 03:47写道:
Hello Kairui,
Quoted 17 lines · Kairui Song
mm/workingset.c +12
-1@@ -226,8 +227,103 @@ static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, *workingsetp = workingset; } -#ifdef CONFIG_LRU_GEN +/* +* Get the distance reading at eviction time. +*/ +static inline unsigned long lru_eviction (struct lruvec *lruvec, +int bits,int bucket_order) +{ +unsigned long eviction =atomic_long_read (&lruvec->nonresident_age); + + eviction >>= bucket_order; + eviction &= ~0UL >> (BITS_PER_LONG - bits); + +return eviction;mm/workingset.c static void unpack_shadow(void *shadow, int *memcgidp, pg_data_t **pgdat, · line 251
Quoted 10 lines
mm/workingset.c +10
-0+} + +/* +* Calculate and test refault distance +*/ +static inline bool lru_refault (struct mem_cgroup *memcg, +struct lruvec *lruvec, +unsigned long eviction,bool file, +int bits,int bucket_order) +{This patch changes the refault decision making at the same time as it moves the functions around in preparation for use by MGLRU. This makes it difficult to review the exact changes to the current algorithm.
Quoted 1 line
Can you please separate those two components?
Hi, thanks for the review.
Quoted 9 lines
Step 1: Change the existing logic in its place to the new algorithm. Please also update the big comment on how everything works at the same time (IOW, merge patch #2 into this one).
Step 2: Add a new patch to refactor for MGLRU right before the patch that will use those functions. As far as I can see, that's the last patch in the series. So the refactor should be second-to-last.
Good suggestion, I'll split these parts into individual patches and merge the comments.
Quoted 1 line
Thanks
kernel test robot
build botRe: [RFC PATCH v2 1/5] workingset: simplify and use a more intuitive model
In reply to Kairui Songkernel test robot noticed the following build warnings:
Hi Kairui,
[This is a private test report for your RFC patch.] kernel test robot noticed the following build warnings:
[auto build test WARNING on linus/master]
[also build test WARNING on v6.6-rc1]
[cannot apply to akpm-mm/mm-everything next-20230912]
[If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Kairui-Song/workingset-simplify-and-use-a-more-intuitive-model/20230913-024648 base: linus/master patch link: https://lore.kernel.org/r/20230912184511.49333-2-ryncsn%40gmail.com patch subject: [RFC PATCH v2 1/5] workingset: simplify and use a more intuitive model config: i386-tinyconfig (https://download.01.org/0day-ci/archive/20230913/[email protected]/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20230913/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
mm/workingset.c: In function 'lru_refault':
>> mm/workingset.c:253:43: warning: unused variable 'inactive' [-Wunused-variable]
253 | unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0;
| ^~~~~~~~
>> mm/workingset.c:253:23: warning: unused variable 'workingset' [-Wunused-variable]
253 | unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0;
| ^~~~~~~~~~vim +/inactive +253 mm/workingset.c
243
244 /*
245 * Calculate and test refault distance
246 */
247 static inline bool lru_refault(struct mem_cgroup *memcg,
248 struct lruvec *lruvec,
249 unsigned long eviction, bool file,
250 int bits, int bucket_order)
251 {
252 unsigned long refault, distance;
> 253 unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0; 254
255 eviction <<= bucket_order;
256 refault = atomic_long_read(&lruvec->nonresident_age);
257
258 /*
259 * The unsigned subtraction here gives an accurate distance
260 * across nonresident_age overflows in most cases. There is a
261 * special case: usually, shadow entries have a short lifetime
262 * and are either refaulted or reclaimed along with the inode
263 * before they get too old. But it is not impossible for the
264 * nonresident_age to lap a shadow entry in the field, which
265 * can then result in a false small refault distance, leading
266 * to a false activation should this old entry actually
267 * refault again. However, earlier kernels used to deactivate
268 * unconditionally with *every* reclaim invocation for the
269 * longest time, so the occasional inappropriate activation
270 * leading to pressure on the active list is not a problem.
271 */
272 distance = (refault - eviction) & (~0UL >> (BITS_PER_LONG - bits));
273
274 active = lruvec_page_state(lruvec, NR_ACTIVE_FILE);
275 inactive_file = lruvec_page_state(lruvec, NR_INACTIVE_FILE);
276 if (mem_cgroup_get_nr_swap_pages(memcg) > 0) {
277 active += lruvec_page_state(lruvec, NR_ACTIVE_ANON);
278 inactive_anon = lruvec_page_state(lruvec, NR_INACTIVE_ANON);
279 }
280
281 /*
282 * Compare the distance to the existing workingset size. We
283 * don't activate pages that couldn't stay resident even if
284 * all the memory was available to the workingset. Whether
285 * workingset competition needs to consider anon or not depends
286 * on having free swap space.
287 *
288 * When there are already enough active pages, be less aggressive
289 * on reactivating pages, challenge an already established set of
290 * active pages with one time refaulted page may not be a good idea.
291 */
292 if (active >= (inactive_anon + inactive_file))
293 return distance < inactive_anon + inactive_file;
294 else
295 return distance < active + (file ? inactive_anon : inactive_file);
296 }
297
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
RFC PATCH v2 2/5 workingset: update comment in workingset.c
In reply to Kairui SongFrom: Kairui Song <[email protected]>
Update the comments according to new implementation.
--- mm/workingset.c | 98 ++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 54 deletions(-)
mm/workingset.c +44 -54
@@ -64,74 +64,64 @@* thrashing on the inactive list, after which refaulting pages can be * activated optimistically to compete with the existing active pages. * -* Approximating inactive page access frequency - Observations: +* For such approximation, we introduce a counter `nonresistence_age` (NA) +* here. This counter increases each time a page is evicted, and each evicted +* page will have a shadow that stores the counter reading at the eviction +* time as a timestamp. So when an evicted page was faulted again, we have: * -* 1. When a page is accessed for the first time, it is added to the -* head of the inactive list, slides every existing inactive page -* towards the tail by one slot, and pushes the current tail page -* out of memory. +* Let SP = ((NA's reading @ current) - (NA's reading @ eviction)) * -* 2. When a page is accessed for the second time, it is promoted to -* the active list, shrinking the inactive list by one slot. This -* also slides all inactive pages that were faulted into the cache -* more recently than the activated page towards the tail of the -* inactive list. +* +-memory available to cache-+ +* | | +* +-------------------------+===============+===========+ +* | * shadows O O O | INACTIVE | ACTIVE | +* +-+-----------------------+===============+===========+ +* | | +* +-----------------------+ +* | SP +* fault page O -> Hole left by previously faulted in pages +* * -> The page corresponding to SP * -* Thus: +* Here SP can stands for how far the current workflow could push a page +* out of available memory. Since all evicted page was once head of +* INACTIVE list, the page could have such an access distance of: * -* 1. The sum of evictions and activations between any two points in -* time indicate the minimum number of inactive pages accessed in -* between. +* SP + NR_INACTIVE * -* 2. Moving one inactive page N page slots towards the tail of the -* list requires at least N inactive page accesses. +* So if: * -* Combining these: +* SP + NR_INACTIVE < NR_INACTIVE + NR_ACTIVE * -* 1. When a page is finally evicted from memory, the number of -* inactive pages accessed while the page was in cache is at least -* the number of page slots on the inactive list. +* Which can be simplified to: * -* 2. In addition, measuring the sum of evictions and activations (E) -* at the time of a page's eviction, and comparing it to another -* reading (R) at the time the page faults back into memory tells -* the minimum number of accesses while the page was not cached. -* This is called the refault distance. +* SP < NR_ACTIVE * -* Because the first access of the page was the fault and the second -* access the refault, we combine the in-cache distance with the -* out-of-cache distance to get the complete minimum access distance -* of this page: +* Then the page is worth getting re-activated to start from ACTIVE part, +* since the access distance is shorter than total memory to make it stay. * -* NR_inactive + (R - E) +* And since this is only an estimation, based on several hypotheses, and +* it could break the ability of LRU to distinguish a workingset out of +* caches, so throttle this by two factors: * -* And knowing the minimum access distance of a page, we can easily -* tell if the page would be able to stay in cache assuming all page -* slots in the cache were available: +* 1. Notice that re-faulted in pages may leave "holes" on the shadow +* part of LRU, that part is left unhandled on purpose to decrease +* re-activate rate for pages that have a large SP value (the larger +* SP value a page have, the more likely it will be affected by such +* holes). +* 2. When the ACTIVE part of LRU is long enough, chanllaging ACTIVE pages +* by re-activating a one-time faulted previously INACTIVE page may not +* be a good idea, so throttle the re-activation when ACTIVE > INACTIVE +* by comparing with INACTIVE instead. * -* NR_inactive + (R - E) <= NR_inactive + NR_active +* Combined all above, we have: +* Upon refault, if any of the following conditions is met, mark the page +* as active: * -* If we have swap we should consider about NR_inactive_anon and -* NR_active_anon, so for page cache and anonymous respectively: -* -* NR_inactive_file + (R - E) <= NR_inactive_file + NR_active_file -* + NR_inactive_anon + NR_active_anon -* -* NR_inactive_anon + (R - E) <= NR_inactive_anon + NR_active_anon -* + NR_inactive_file + NR_active_file -* -* Which can be further simplified to: -* -* (R - E) <= NR_active_file + NR_inactive_anon + NR_active_anon -* -* (R - E) <= NR_active_anon + NR_inactive_file + NR_active_file -* -* Put into words, the refault distance (out-of-cache) can be seen as -* a deficit in inactive list space (in-cache). If the inactive list -* had (R - E) more page slots, the page would not have been evicted -* in between accesses, but activated instead. And on a full system, -* the only thing eating into inactive list space is active pages. +* - If ACTIVE LRU is low (NR_ACTIVE < NR_INACTIVE), check if: +* SP < NR_ACTIVE * +* - If ACTIVE LRU is high (NR_ACTIVE >= NR_INACTIVE), check if: +* SP < NR_INACTIVE * * Refaulting inactive pages *
Signature · 1 line
2.41.0
RFC PATCH v2 3/5 workingset: simplify lru_gen_test_recent
In reply to Kairui SongFrom: Kairui Song <[email protected]>
Simplify the code, move some common path into its caller, prepare for following commits.
--- mm/workingset.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-)
mm/workingset.c +13 -17
@@ -345,42 +345,38 @@ static void *lru_gen_eviction(struct folio *folio)* Tests if the shadow entry is for a folio that was recently evicted. * Fills in @lruvec, @token, @workingset with the values unpacked from shadow. */ -static bool lru_gen_test_recent (void *shadow,bool file,struct lruvec **lruvec, -unsigned long *token,bool *workingset) +static bool lru_gen_test_recent (struct lruvec *lruvec,bool file, +unsigned long token) { -int memcg_id;unsigned long min_seq; -struct mem_cgroup *memcg; -struct pglist_data *pgdat; -unpack_shadow (shadow, &memcg_id, &pgdat, token, workingset); - - memcg =mem_cgroup_from_id (memcg_id); - *lruvec =mem_cgroup_lruvec (memcg, pgdat); - - min_seq =READ_ONCE ((*lruvec)->lrugen.min_seq[file]); -return (*token >>LRU_REFS_WIDTH ) == (min_seq & (EVICTION_MASK >>LRU_REFS_WIDTH )); + min_seq =READ_ONCE (lruvec->lrugen.min_seq[file]); +return (token >>LRU_REFS_WIDTH ) == (min_seq & (EVICTION_MASK >>LRU_REFS_WIDTH )); }static void lru_gen_refault (struct folio *folio,void *shadow) { +int memcgid;bool recent; -int hist, tier, refs;bool workingset;unsigned long token; +int hist, tier, refs;struct lruvec *lruvec; +struct pglist_data *pgdat;struct lru_gen_folio *lrugen;int type =folio_is_file_lru (folio);int delta =folio_nr_pages (folio);rcu_read_lock (); - recent =lru_gen_test_recent (shadow, type, &lruvec, &token, &workingset); +unpack_shadow (shadow, &memcgid, &pgdat, &token, &workingset); + lruvec =mem_cgroup_lruvec (mem_cgroup_from_id (memcgid), pgdat);if (lruvec !=folio_lruvec (folio)) goto unlock;mod_lruvec_state (lruvec,WORKINGSET_REFAULT_BASE + type, delta); + recent =lru_gen_test_recent (lruvec, type, token);if (!recent) goto unlock;
@@ -480,9 +476,6 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset)struct pglist_data *pgdat;unsigned long eviction; -if (lru_gen_enabled ()) -return lru_gen_test_recent (shadow, file, &eviction_lruvec, &eviction, workingset); -unpack_shadow (shadow, &memcgid, &pgdat, &eviction, workingset);/*
@@ -506,6 +499,9 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset)return false; eviction_lruvec =mem_cgroup_lruvec (eviction_memcg, pgdat); +if (lru_gen_enabled ()) +return lru_gen_test_recent (eviction_lruvec, file, eviction); +return lru_refault (eviction_memcg, eviction_lruvec, eviction, file,EVICTION_BITS , bucket_order); }
Signature · 1 line
2.41.0
kernel test robot
build botRe: [RFC PATCH v2 3/5] workingset: simplify lru_gen_test_recent
kernel test robot noticed the following build errors:
Hi Kairui,
[This is a private test report for your RFC patch.] kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on v6.6-rc1]
[cannot apply to akpm-mm/mm-everything next-20230912]
[If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Kairui-Song/workingset-simplify-and-use-a-more-intuitive-model/20230913-024648 base: linus/master patch link: https://lore.kernel.org/r/20230912184511.49333-4-ryncsn%40gmail.com patch subject: [RFC PATCH v2 3/5] workingset: simplify lru_gen_test_recent config: i386-tinyconfig (https://download.01.org/0day-ci/archive/20230913/[email protected]/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20230913/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All error/warnings (new ones prefixed by >>):
mm/workingset.c: In function 'lru_refault':
mm/workingset.c:243:43: warning: unused variable 'inactive' [-Wunused-variable]
243 | unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0;
| ^~~~~~~~
mm/workingset.c:243:23: warning: unused variable 'workingset' [-Wunused-variable]
243 | unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0;
| ^~~~~~~~~~
mm/workingset.c: In function 'workingset_test_recent':
>> mm/workingset.c:503:67: warning: passing argument 3 of 'lru_gen_test_recent' makes pointer from integer without a cast [-Wint-conversion]
503 | return lru_gen_test_recent(eviction_lruvec, file, eviction);
| ^~~~~~~~
| |
| long unsigned int
mm/workingset.c:415:74: note: expected 'struct lruvec **' but argument is of type 'long unsigned int'
415 | static bool lru_gen_test_recent(void *shadow, bool file, struct lruvec **lruvec,
| ~~~~~~~~~~~~~~~~^~~~~~
>> mm/workingset.c:503:24: error: too few arguments to function 'lru_gen_test_recent'
503 | return lru_gen_test_recent(eviction_lruvec, file, eviction);
| ^~~~~~~~~~~~~~~~~~~ mm/workingset.c:415:13: note: declared here
415 | static bool lru_gen_test_recent(void *shadow, bool file, struct lruvec **lruvec,
| ^~~~~~~~~~~~~~~~~~~
vim +/lru_gen_test_recent +503 mm/workingset.c
459
460 /**
461 * workingset_test_recent - tests if the shadow entry is for a folio that was
462 * recently evicted. Also fills in @workingset with the value unpacked from
463 * shadow.
464 * @shadow: the shadow entry to be tested.
465 * @file: whether the corresponding folio is from the file lru.
466 * @workingset: where the workingset value unpacked from shadow should
467 * be stored.
468 *
469 * Return: true if the shadow is for a recently evicted folio; false otherwise.
470 */
471 bool workingset_test_recent(void *shadow, bool file, bool *workingset)
472 {
473 struct mem_cgroup *eviction_memcg;
474 struct lruvec *eviction_lruvec;
475 int memcgid;
476 struct pglist_data *pgdat;
477 unsigned long eviction;
478
479 unpack_shadow(shadow, &memcgid, &pgdat, &eviction, workingset);
480
481 /*
482 * Look up the memcg associated with the stored ID. It might
483 * have been deleted since the folio's eviction.
484 *
485 * Note that in rare events the ID could have been recycled
486 * for a new cgroup that refaults a shared folio. This is
487 * impossible to tell from the available data. However, this
488 * should be a rare and limited disturbance, and activations
489 * are always speculative anyway. Ultimately, it's the aging
490 * algorithm's job to shake out the minimum access frequency
491 * for the active cache.
492 *
493 * XXX: On !CONFIG_MEMCG, this will always return NULL; it
494 * would be better if the root_mem_cgroup existed in all
495 * configurations instead.
496 */
497 eviction_memcg = mem_cgroup_from_id(memcgid);
498 if (!mem_cgroup_disabled() && !eviction_memcg)
499 return false;
500 eviction_lruvec = mem_cgroup_lruvec(eviction_memcg, pgdat);
501
502 if (lru_gen_enabled())
> 503 return lru_gen_test_recent(eviction_lruvec, file, eviction);
504
505 return lru_refault(eviction_memcg, eviction_lruvec, eviction, file,
506 EVICTION_BITS, bucket_order);
507 }
508
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
kernel test robot
build botRe: [RFC PATCH v2 3/5] workingset: simplify lru_gen_test_recent
In reply to Kairui Songkernel test robot noticed the following build errors:
Hi Kairui,
[This is a private test report for your RFC patch.] kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on v6.6-rc1]
[cannot apply to akpm-mm/mm-everything next-20230913]
[If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Kairui-Song/workingset-simplify-and-use-a-more-intuitive-model/20230913-024648 base: linus/master patch link: https://lore.kernel.org/r/20230912184511.49333-4-ryncsn%40gmail.com patch subject: [RFC PATCH v2 3/5] workingset: simplify lru_gen_test_recent config: s390-randconfig-r013-20230913 (https://download.01.org/0day-ci/archive/20230913/[email protected]/config) compiler: clang version 15.0.7 (https://github.com/llvm/llvm-project.git 8dfdcc7b7bf66834a761bd8de445840ef68e4d1a) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20230913/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>):
mm/workingset.c:243:16: warning: unused variable 'workingset' [-Wunused-variable]
unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0;
^
mm/workingset.c:243:36: warning: unused variable 'inactive' [-Wunused-variable]
unsigned long workingset, active, inactive, inactive_file, inactive_anon = 0;
^
>> mm/workingset.c:503:61: error: too few arguments to function call, expected 5, have 3
return lru_gen_test_recent(eviction_lruvec, file, eviction);
~~~~~~~~~~~~~~~~~~~ ^
mm/workingset.c:415:13: note: 'lru_gen_test_recent' declared here
static bool lru_gen_test_recent(void *shadow, bool file, struct lruvec **lruvec,
^2 warnings and 1 error generated.
vim +503 mm/workingset.c
459
460 /**
461 * workingset_test_recent - tests if the shadow entry is for a folio that was
462 * recently evicted. Also fills in @workingset with the value unpacked from
463 * shadow.
464 * @shadow: the shadow entry to be tested.
465 * @file: whether the corresponding folio is from the file lru.
466 * @workingset: where the workingset value unpacked from shadow should
467 * be stored.
468 *
469 * Return: true if the shadow is for a recently evicted folio; false otherwise.
470 */
471 bool workingset_test_recent(void *shadow, bool file, bool *workingset)
472 {
473 struct mem_cgroup *eviction_memcg;
474 struct lruvec *eviction_lruvec;
475 int memcgid;
476 struct pglist_data *pgdat;
477 unsigned long eviction;
478
479 unpack_shadow(shadow, &memcgid, &pgdat, &eviction, workingset);
480
481 /*
482 * Look up the memcg associated with the stored ID. It might
483 * have been deleted since the folio's eviction.
484 *
485 * Note that in rare events the ID could have been recycled
486 * for a new cgroup that refaults a shared folio. This is
487 * impossible to tell from the available data. However, this
488 * should be a rare and limited disturbance, and activations
489 * are always speculative anyway. Ultimately, it's the aging
490 * algorithm's job to shake out the minimum access frequency
491 * for the active cache.
492 *
493 * XXX: On !CONFIG_MEMCG, this will always return NULL; it
494 * would be better if the root_mem_cgroup existed in all
495 * configurations instead.
496 */
497 eviction_memcg = mem_cgroup_from_id(memcgid);
498 if (!mem_cgroup_disabled() && !eviction_memcg)
499 return false;
500 eviction_lruvec = mem_cgroup_lruvec(eviction_memcg, pgdat);
501
502 if (lru_gen_enabled())
> 503 return lru_gen_test_recent(eviction_lruvec, file, eviction);
504
505 return lru_refault(eviction_memcg, eviction_lruvec, eviction, file,
506 EVICTION_BITS, bucket_order);
507 }
508
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
RFC PATCH v2 4/5 lru_gen: convert avg_total and avg_refaulted to atomic
In reply to Kairui SongFrom: Kairui Song <[email protected]>
No feature change, prepare for later patch.
--- include/linux/mmzone.h | 4 ++-- mm/vmscan.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-)
include/linux/mmzone.h +2 -2
@@ -425,9 +425,9 @@ struct lru_gen_folio {/* the multi-gen LRU sizes, eventually consistent */ long nr_pages[MAX_NR_GENS ][ANON_AND_FILE ][MAX_NR_ZONES ];/* the exponential moving average of refaulted */ -unsigned long avg_refaulted[ANON_AND_FILE ][MAX_NR_TIERS ]; +atomic_long_t avg_refaulted[ANON_AND_FILE ][MAX_NR_TIERS ];/* the exponential moving average of evicted+protected */ -unsigned long avg_total[ANON_AND_FILE ][MAX_NR_TIERS ]; +atomic_long_t avg_total[ANON_AND_FILE ][MAX_NR_TIERS ];/* the first tier doesn't need protection, hence the minus one */ unsigned long protected[NR_HIST_GENS ][ANON_AND_FILE ][MAX_NR_TIERS -1 ];/* can be modified without holding the LRU lock */
mm/vmscan.c +8 -8
@@ -3705,9 +3705,9 @@ static void read_ctrl_pos(struct lruvec *lruvec, int type, int tier, int gain,struct lru_gen_folio *lrugen = &lruvec->lrugen;int hist =lru_hist_from_seq (lrugen->min_seq[type]); - pos->refaulted = lrugen->avg_refaulted[type][tier] + + pos->refaulted =atomic_long_read (&lrugen->avg_refaulted[type][tier]) +atomic_long_read (&lrugen->refaulted[hist][type][tier]); - pos->total = lrugen->avg_total[type][tier] + + pos->total =atomic_long_read (&lrugen->avg_total[type][tier]) +atomic_long_read (&lrugen->evicted[hist][type][tier]);if (tier) pos->total += lrugen->protected[hist][type][tier -1 ];
@@ -3732,15 +3732,15 @@ static void reset_ctrl_pos(struct lruvec *lruvec, int type, bool carryover)if (carryover) {unsigned long sum; - sum = lrugen->avg_refaulted[type][tier] + + sum =atomic_long_read (&lrugen->avg_refaulted[type][tier]) +atomic_long_read (&lrugen->refaulted[hist][type][tier]); -WRITE_ONCE (lrugen->avg_refaulted[type][tier], sum /2 ); +atomic_long_set (&lrugen->avg_refaulted[type][tier], sum /2 ); - sum = lrugen->avg_total[type][tier] + + sum =atomic_long_read (&lrugen->avg_total[type][tier]) +atomic_long_read (&lrugen->evicted[hist][type][tier]);if (tier) sum += lrugen->protected[hist][type][tier -1 ]; -WRITE_ONCE (lrugen->avg_total[type][tier], sum /2 ); +atomic_long_set (&lrugen->avg_total[type][tier], sum /2 ); }if (clear) {
@@ -5885,8 +5885,8 @@ static void lru_gen_seq_show_full(struct seq_file *m, struct lruvec *lruvec,if (seq == max_seq) { s ="RT " ; - n[0 ] =READ_ONCE (lrugen->avg_refaulted[type][tier]); - n[1 ] =READ_ONCE (lrugen->avg_total[type][tier]); + n[0 ] =atomic_long_read (&lrugen->avg_refaulted[type][tier]); + n[1 ] =atomic_long_read (&lrugen->avg_total[type][tier]); }else if (seq == min_seq[type] ||NR_HIST_GENS >1 ) { s ="rep" ; n[0 ] =atomic_long_read (&lrugen->refaulted[hist][type][tier]);
Signature · 1 line
2.41.0
RFC PATCH v2 5/5 workingset, lru_gen: apply refault-distance based re-activation
In reply to Kairui SongFrom: Kairui Song <[email protected]>
I noticed MGLRU not working very well on certain workflows, which is observed on some heavily stressed databases. That is when the file page workingset size exceeds total memory, and the access distance (the left-shift time of a page before it gets activated, considering LRU starts from right) of file pages also larger than total memory. All file pages are stuck on the oldest generation and getting read-in then evicted permutably. Despite anon pages being idle, they never get aged. PID controller didn't kickin until there are some minor access pattern changes. And file pages are not promoted or reused.
Even though the memory can't cover the whole workingset, the refault-distance based re-activation can help hold part of the workingset in-memory to help reduce the IO workload significantly.
So apply it for MGLRU as well. The updated refault-distance model fits well for MGLRU in most cases, if we just consider the last two generation as the inactive LRU and the first two generations as active LRU.
Some adjustment is done to fit the logic better, also make the refault-distance contributed to page tiering and PID refault detection of MGLRU:
- If a tier-0 page have a qualified refault-distance, just promote it to higher tier, send it to second oldest gen.
- If a tier >= 1 page have a qualified refault-distance, mark it as active and send it to youngest gen.
- Increase the reference of every page that have a qualified refault-distance and increase the PID countroled refault rate of the updated tier.
Following benchmark showed improvement. To simulate the workflow, I setup a 3-replicated mongodb cluster, each use 5 gb of cache and 10g of oplog, on a 32G VM. The benchmark is done using
https://github.com/apavlo/py-tpcc.git, modified to run STOCK_LEVEL query only, for simulating slow query and get a stable result.
Before the patch (with 10G swap, the result won't change whether swap is on or not):
$ tpcc.py --config=mongodb.config mongodb --duration=900 --warehouses=500 --clients=30
==================================================================
Execution Results after 904 seconds
------------------------------------------------------------------
Executed Time (µs) Rate
STOCK_LEVEL 503 27150226136.4 0.02 txn/s
------------------------------------------------------------------
TOTAL 503 27150226136.4 0.02 txn/s$ cat /proc/vmstat | grep working workingset_nodes 53391 workingset_refault_anon 0 workingset_refault_file 23856735 workingset_activate_anon 0 workingset_activate_file 23845737 workingset_restore_anon 0 workingset_restore_file 18280692 workingset_nodereclaim 1024
$ free -m
total used free shared buff/cache available
Mem: 31837 6752 379 23 24706 24607
Swap: 10239 0 10239After the patch (with 10G swap on same disk, similiar result using ZRAM):
$ tpcc.py --config=mongodb.config mongodb --duration=900 --warehouses=500 --clients=30
==================================================================
Execution Results after 903 seconds
------------------------------------------------------------------
Executed Time (µs) Rate
STOCK_LEVEL 2575 27094953498.8 0.10 txn/s
------------------------------------------------------------------
TOTAL 2575 27094953498.8 0.10 txn/s$ cat /proc/vmstat | grep working workingset_nodes 78249 workingset_refault_anon 10139 workingset_refault_file 23001863 workingset_activate_anon 7238 workingset_activate_file 6718032 workingset_restore_anon 7432 workingset_restore_file 6719406 workingset_nodereclaim 9747
$ free -m
total used free shared buff/cache available
Mem: 31837 7376 320 3 24140 24014
Swap: 10239 1662 8577The performance is 5x times better than before, and the idle anon pages now can get swapped out as expected. The result is also better with lower test stress, testing with lower stress also shows a improvement.
I also checked the benchmark with memtier/memcached and fio, using similar setup as in commit ac35a4902374 but scaled down to fit in my test environment:
memtier test (16G ramdisk as swap, 2G memcg limit, VM on a EPYC 7K62):
memcached -u nobody -m 16384 -s /tmp/memcached.socket -a 0766 \
-t 12 -B binary &
memtier_benchmark -S /tmp/memcached.socket -P memcache_binary -n allkeys\
--key-minimum=1 --key-maximum=24000000 --key-pattern=P:P -c 1 \
-t 12 --ratio 1:0 --pipeline 8 -d 2000 -x 6
fio test (16G ramdisk on /mnt, 4G memcg limit, VM on a EPYC 7K62):
fio -name=refault --numjobs=14 --directory=/mnt --size=1024m \
--buffered=1 --ioengine=io_uring --iodepth=128 \
--iodepth_batch_submit=32 --iodepth_batch_complete=32 \
--rw=randread --random_distribution=random --norandommap \
--time_based --ramp_time=5m --runtime=5m --group_reporting
mysql test (15G buffer pool with 16G memcg limit, VM on a EPYC 7K62):
sysbench /usr/share/sysbench/oltp_read_only.lua <auth and db params> \
--tables=48 --table-size=2000000 --threads=32 --time=1800Before this patch:
memtier: 379329.77 op/s
fio: 5786.8k iops
mysql: 150190.43 qps
After this patch:
memtier: 373877.41 op/s
fio: 5805.5k iops
mysql: 150220.93 qps
The test looks ok except a bit extra overhead introduced by atomic operations introduced, there seems to be no LRU accuracy drop.
--- mm/workingset.c | 78 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 25 deletions(-)
mm/workingset.c +53 -25
@@ -175,6 +175,7 @@ MEM_CGROUP_ID_SHIFT)#define EVICTION_BITS (BITS_PER_LONG - (EVICTION_SHIFT))#define EVICTION_MASK (~0UL >> EVICTION_SHIFT) +#define LRU_GEN_EVICTION_BITS (EVICTION_BITS - LRU_REFS_WIDTH - LRU_GEN_WIDTH)/* * Eviction timestamps need to be able to cover the full range of
@@ -185,6 +186,7 @@* evictions into coarser buckets by shaving off lower timestamp bits. */ static unsigned int bucket_order__read_mostly ; +static unsigned int lru_gen_bucket_order__read_mostly ;static void *pack_shadow (int memcgid,pg_data_t *pgdat,unsigned long eviction,bool workingset)
@@ -240,7 +242,7 @@ static inline bool lru_refault(struct mem_cgroup *memcg, int bits, int bucket_order) {unsigned long refault, distance; -unsigned long workingset, active, inactive, inactive_file, inactive_anon =0 ; +unsigned long active, inactive_file, inactive_anon =0 ; eviction <<= bucket_order; refault =atomic_long_read (&lruvec->nonresident_age);
@@ -280,7 +282,7 @@ static inline bool lru_refault(struct mem_cgroup *memcg,* active pages with one time refaulted page may not be a good idea. */ if (active >= (inactive_anon + inactive_file)) -return distance < inactive_anon + inactive_file; +return distance < (inactive_anon + inactive_file);else return distance < active + (file ? inactive_anon : inactive_file); }
@@ -333,10 +335,14 @@ static void *lru_gen_eviction(struct folio *folio) lruvec =mem_cgroup_lruvec (memcg, pgdat); lrugen = &lruvec->lrugen; min_seq =READ_ONCE (lrugen->min_seq[type]); + token = (min_seq <<LRU_REFS_WIDTH ) |max (refs -1 ,0 ); + token <<=LRU_GEN_EVICTION_BITS ; + token |=lru_eviction (lruvec,LRU_GEN_EVICTION_BITS , lru_gen_bucket_order); hist =lru_hist_from_seq (min_seq);atomic_long_add (delta, &lrugen->evicted[hist][type][tier]); +workingset_age_nonresident (lruvec,folio_nr_pages (folio));return pack_shadow (mem_cgroup_id (memcg), pgdat, token, refs); }
@@ -351,44 +357,55 @@ static bool lru_gen_test_recent(struct lruvec *lruvec, bool file,unsigned long min_seq; min_seq =READ_ONCE (lruvec->lrugen.min_seq[file]); + token >>=LRU_GEN_EVICTION_BITS ;return (token >>LRU_REFS_WIDTH ) == (min_seq & (EVICTION_MASK >>LRU_REFS_WIDTH )); }static void lru_gen_refault (struct folio *folio,void *shadow) {int memcgid; -bool recent; +bool refault;bool workingset;unsigned long token; +bool recent = false; +int refault_tier =0 ;int hist, tier, refs;struct lruvec *lruvec; +struct mem_cgroup *memcg;struct pglist_data *pgdat;struct lru_gen_folio *lrugen;int type =folio_is_file_lru (folio);int delta =folio_nr_pages (folio); -rcu_read_lock (); -unpack_shadow (shadow, &memcgid, &pgdat, &token, &workingset); - lruvec =mem_cgroup_lruvec (mem_cgroup_from_id (memcgid), pgdat); -if (lruvec !=folio_lruvec (folio)) - goto unlock; + memcg =mem_cgroup_from_id (memcgid); + lruvec =mem_cgroup_lruvec (memcg, pgdat); +/* memcg can be NULL, go through lruvec */ + memcg =lruvec_memcg (lruvec);mod_lruvec_state (lruvec,WORKINGSET_REFAULT_BASE + type, delta); - - recent =lru_gen_test_recent (lruvec, type, token); -if (!recent) - goto unlock; + refault =lru_refault (memcg, lruvec, token, type, +LRU_GEN_EVICTION_BITS , lru_gen_bucket_order); +if (lruvec ==folio_lruvec (folio)) + recent =lru_gen_test_recent (lruvec, type, token); +if (!recent && !refault) +return ; lrugen = &lruvec->lrugen; - hist =lru_hist_from_seq (READ_ONCE (lrugen->min_seq[type]));/* see the comment in folio_lru_refs() */ + token >>=LRU_GEN_EVICTION_BITS ; refs = (token & (BIT (LRU_REFS_WIDTH ) -1 )) + workingset; tier =lru_tier_from_refs (refs); - -atomic_long_add (delta, &lrugen->refaulted[hist][type][tier]); -mod_lruvec_state (lruvec,WORKINGSET_ACTIVATE_BASE + type, delta); + refault_tier = tier; + +if (refault) { +if (refs) +folio_set_active (folio); +if (refs !=BIT (LRU_REFS_WIDTH )) + refault_tier =lru_tier_from_refs (refs +1 ); +mod_lruvec_state (lruvec,WORKINGSET_ACTIVATE_BASE + type, delta); + }/* * Count the following two cases as stalls:
@@ -397,12 +414,17 @@ static void lru_gen_refault(struct folio *folio, void *shadow)* 2. For pages accessed multiple times through file descriptors, * numbers of accesses might have been out of the range. */ -if (lru_gen_in_fault () || refs ==BIT (LRU_REFS_WIDTH )) { +if (refault ||lru_gen_in_fault () || refs ==BIT (LRU_REFS_WIDTH )) {folio_set_workingset (folio);mod_lruvec_state (lruvec,WORKINGSET_RESTORE_BASE + type, delta); } -unlock: - rcu_read_unlock(); + +if (recent && refault_tier == tier) { +atomic_long_add (delta, &lrugen->refaulted[hist][type][tier]); + }else { +atomic_long_add (delta, &lrugen->avg_total[type][refault_tier]); +atomic_long_add (delta, &lrugen->avg_refaulted[type][refault_tier]); + } }#else /* !CONFIG_LRU_GEN */
@@ -524,16 +546,15 @@ void workingset_refault(struct folio *folio, void *shadow)bool workingset;long nr; -if (lru_gen_enabled ()) { -lru_gen_refault (folio, shadow); -return ; - } -/* Flush stats (and potentially sleep) before holding RCU read lock */ mem_cgroup_flush_stats_ratelimited (); -rcu_read_lock (); +if (lru_gen_enabled ()) { +lru_gen_refault (folio, shadow); + goto out; + } +/* * The activation decision for this folio is made at the level * where the eviction occurred, as that is where the LRU order
@@ -780,6 +801,13 @@ static int __init workingset_init(void)pr_info ("workingset: timestamp_bits=%d max_order=%d bucket_order=%u\n" ,EVICTION_BITS , max_order, bucket_order); +#ifdef CONFIG_LRU_GEN +if (max_order >LRU_GEN_EVICTION_BITS ) + lru_gen_bucket_order = max_order -LRU_GEN_EVICTION_BITS ; +pr_info ("workingset: lru_gen_timestamp_bits=%d lru_gen_bucket_order=%u\n" , +LRU_GEN_EVICTION_BITS , lru_gen_bucket_order); +#endif + ret =prealloc_shrinker (&workingset_shadow_shrinker,"mm-shadow" );if (ret) goto err;
Signature · 1 line
2.41.0
kernel test robot
build botRe: [RFC PATCH v2 5/5] workingset, lru_gen: apply refault-distance based re-activation
kernel test robot noticed the following build warnings:
Hi Kairui,
[This is a private test report for your RFC patch.] kernel test robot noticed the following build warnings:
[auto build test WARNING on linus/master]
[also build test WARNING on v6.6-rc1]
[cannot apply to akpm-mm/mm-everything next-20230912]
[If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Kairui-Song/workingset-simplify-and-use-a-more-intuitive-model/20230913-024648 base: linus/master patch link: https://lore.kernel.org/r/20230912184511.49333-6-ryncsn%40gmail.com patch subject: [RFC PATCH v2 5/5] workingset, lru_gen: apply refault-distance based re-activation config: i386-tinyconfig (https://download.01.org/0day-ci/archive/20230913/[email protected]/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20230913/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
mm/workingset.c: In function 'workingset_test_recent':
mm/workingset.c:525:67: warning: passing argument 3 of 'lru_gen_test_recent' makes pointer from integer without a cast [-Wint-conversion]
525 | return lru_gen_test_recent(eviction_lruvec, file, eviction);
| ^~~~~~~~
| |
| long unsigned int
mm/workingset.c:437:74: note: expected 'struct lruvec **' but argument is of type 'long unsigned int'
437 | static bool lru_gen_test_recent(void *shadow, bool file, struct lruvec **lruvec,
| ~~~~~~~~~~~~~~~~^~~~~~
mm/workingset.c:525:24: error: too few arguments to function 'lru_gen_test_recent'
525 | return lru_gen_test_recent(eviction_lruvec, file, eviction);
| ^~~~~~~~~~~~~~~~~~~
mm/workingset.c:437:13: note: declared here
437 | static bool lru_gen_test_recent(void *shadow, bool file, struct lruvec **lruvec,
| ^~~~~~~~~~~~~~~~~~~
mm/workingset.c: At top level:
>> mm/workingset.c:189:21: warning: 'lru_gen_bucket_order' defined but not used [-Wunused-variable]
189 | static unsigned int lru_gen_bucket_order __read_mostly;
| ^~~~~~~~~~~~~~~~~~~~vim +/lru_gen_bucket_order +189 mm/workingset.c
179
180 /*
181 * Eviction timestamps need to be able to cover the full range of
182 * actionable refaults. However, bits are tight in the xarray
183 * entry, and after storing the identifier for the lruvec there might
184 * not be enough left to represent every single actionable refault. In
185 * that case, we have to sacrifice granularity for distance, and group
186 * evictions into coarser buckets by shaving off lower timestamp bits.
187 */
188 static unsigned int bucket_order __read_mostly;
> 189 static unsigned int lru_gen_bucket_order __read_mostly;
190
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki