mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-23 17:30:02 +00:00
![Johannes Weiner](/assets/img/avatar_default.png)
We noticed a performance regression when moving hadoop workloads from 3.10 kernels to 4.0 and 4.6. This is accompanied by increased pageout activity initiated by kswapd as well as frequent bursts of allocation stalls and direct reclaim scans. Even lowering the dirty ratios to the equivalent of less than 1% of memory would not eliminate the issue, suggesting that dirty pages concentrate where the scanner is looking. This can be traced back to recent efforts of thrash avoidance. Where 3.10 would not detect refaulting pages and continuously supply clean cache to the inactive list, a thrashing workload on 4.0+ will detect and activate refaulting pages right away, distilling used-once pages on the inactive list much more effectively. This is by design, and it makes sense for clean cache. But for the most part our workload's cache faults are refaults and its use-once cache is from streaming writes. We end up with most of the inactive list dirty, and we don't go after the active cache as long as we have use-once pages around. But waiting for writes to avoid reclaiming clean cache that *might* refault is a bad trade-off. Even if the refaults happen, reads are faster than writes. Before getting bogged down on writeback, reclaim should first look at *all* cache in the system, even active cache. To accomplish this, activate pages that are dirty or under writeback when they reach the end of the inactive LRU. The pages are marked for immediate reclaim, meaning they'll get moved back to the inactive LRU tail as soon as they're written back and become reclaimable. But in the meantime, by reducing the inactive list to only immediately reclaimable pages, we allow the scanner to deactivate and refill the inactive list with clean cache from the active list tail to guarantee forward progress. [hannes@cmpxchg.org: update comment] Link: http://lkml.kernel.org/r/20170202191957.22872-8-hannes@cmpxchg.org Link: http://lkml.kernel.org/r/20170123181641.23938-6-hannes@cmpxchg.org Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Acked-by: Minchan Kim <minchan@kernel.org> Acked-by: Michal Hocko <mhocko@suse.com> Acked-by: Hillf Danton <hillf.zj@alibaba-inc.com> Cc: Mel Gorman <mgorman@suse.de> Cc: Rik van Riel <riel@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
129 lines
3.4 KiB
C
129 lines
3.4 KiB
C
#ifndef LINUX_MM_INLINE_H
|
|
#define LINUX_MM_INLINE_H
|
|
|
|
#include <linux/huge_mm.h>
|
|
#include <linux/swap.h>
|
|
|
|
/**
|
|
* page_is_file_cache - should the page be on a file LRU or anon LRU?
|
|
* @page: the page to test
|
|
*
|
|
* Returns 1 if @page is page cache page backed by a regular filesystem,
|
|
* or 0 if @page is anonymous, tmpfs or otherwise ram or swap backed.
|
|
* Used by functions that manipulate the LRU lists, to sort a page
|
|
* onto the right LRU list.
|
|
*
|
|
* We would like to get this info without a page flag, but the state
|
|
* needs to survive until the page is last deleted from the LRU, which
|
|
* could be as far down as __page_cache_release.
|
|
*/
|
|
static inline int page_is_file_cache(struct page *page)
|
|
{
|
|
return !PageSwapBacked(page);
|
|
}
|
|
|
|
static __always_inline void __update_lru_size(struct lruvec *lruvec,
|
|
enum lru_list lru, enum zone_type zid,
|
|
int nr_pages)
|
|
{
|
|
struct pglist_data *pgdat = lruvec_pgdat(lruvec);
|
|
|
|
__mod_node_page_state(pgdat, NR_LRU_BASE + lru, nr_pages);
|
|
__mod_zone_page_state(&pgdat->node_zones[zid],
|
|
NR_ZONE_LRU_BASE + lru, nr_pages);
|
|
}
|
|
|
|
static __always_inline void update_lru_size(struct lruvec *lruvec,
|
|
enum lru_list lru, enum zone_type zid,
|
|
int nr_pages)
|
|
{
|
|
__update_lru_size(lruvec, lru, zid, nr_pages);
|
|
#ifdef CONFIG_MEMCG
|
|
mem_cgroup_update_lru_size(lruvec, lru, zid, nr_pages);
|
|
#endif
|
|
}
|
|
|
|
static __always_inline void add_page_to_lru_list(struct page *page,
|
|
struct lruvec *lruvec, enum lru_list lru)
|
|
{
|
|
update_lru_size(lruvec, lru, page_zonenum(page), hpage_nr_pages(page));
|
|
list_add(&page->lru, &lruvec->lists[lru]);
|
|
}
|
|
|
|
static __always_inline void add_page_to_lru_list_tail(struct page *page,
|
|
struct lruvec *lruvec, enum lru_list lru)
|
|
{
|
|
update_lru_size(lruvec, lru, page_zonenum(page), hpage_nr_pages(page));
|
|
list_add_tail(&page->lru, &lruvec->lists[lru]);
|
|
}
|
|
|
|
static __always_inline void del_page_from_lru_list(struct page *page,
|
|
struct lruvec *lruvec, enum lru_list lru)
|
|
{
|
|
list_del(&page->lru);
|
|
update_lru_size(lruvec, lru, page_zonenum(page), -hpage_nr_pages(page));
|
|
}
|
|
|
|
/**
|
|
* page_lru_base_type - which LRU list type should a page be on?
|
|
* @page: the page to test
|
|
*
|
|
* Used for LRU list index arithmetic.
|
|
*
|
|
* Returns the base LRU type - file or anon - @page should be on.
|
|
*/
|
|
static inline enum lru_list page_lru_base_type(struct page *page)
|
|
{
|
|
if (page_is_file_cache(page))
|
|
return LRU_INACTIVE_FILE;
|
|
return LRU_INACTIVE_ANON;
|
|
}
|
|
|
|
/**
|
|
* page_off_lru - which LRU list was page on? clearing its lru flags.
|
|
* @page: the page to test
|
|
*
|
|
* Returns the LRU list a page was on, as an index into the array of LRU
|
|
* lists; and clears its Unevictable or Active flags, ready for freeing.
|
|
*/
|
|
static __always_inline enum lru_list page_off_lru(struct page *page)
|
|
{
|
|
enum lru_list lru;
|
|
|
|
if (PageUnevictable(page)) {
|
|
__ClearPageUnevictable(page);
|
|
lru = LRU_UNEVICTABLE;
|
|
} else {
|
|
lru = page_lru_base_type(page);
|
|
if (PageActive(page)) {
|
|
__ClearPageActive(page);
|
|
lru += LRU_ACTIVE;
|
|
}
|
|
}
|
|
return lru;
|
|
}
|
|
|
|
/**
|
|
* page_lru - which LRU list should a page be on?
|
|
* @page: the page to test
|
|
*
|
|
* Returns the LRU list a page should be on, as an index
|
|
* into the array of LRU lists.
|
|
*/
|
|
static __always_inline enum lru_list page_lru(struct page *page)
|
|
{
|
|
enum lru_list lru;
|
|
|
|
if (PageUnevictable(page))
|
|
lru = LRU_UNEVICTABLE;
|
|
else {
|
|
lru = page_lru_base_type(page);
|
|
if (PageActive(page))
|
|
lru += LRU_ACTIVE;
|
|
}
|
|
return lru;
|
|
}
|
|
|
|
#define lru_to_page(head) (list_entry((head)->prev, struct page, lru))
|
|
|
|
#endif
|