Skip to contentSkip to conversation

support madvise(MADV_FREE)

RFC Superseded Minchan Kim 1 participant 4 messages Mar 20, 2014

Subsystems (8)

v2 of 17 Superseded Latest is v17 →

lore
Share & tools
lore
#0 lore raw

v2 cover support madvise(MADV_FREE)

This patch is an attempt to support MADV_FREE for Linux.

Rationale is following as.

Allocators call munmap(2) when user call free(3) if ptr is in mmaped area. But munmap isn't cheap because it have to clean up all pte entries, unlinking a vma and returns free pages to page allocator so that overhead would be increased linearly by mmaped area's size. In the end, userspace allocators like MADV_DONTNEED rather than munmap.

Because MADV_DONTNEED holds read-side lock of mmap_sem so other threads of the process could go with concurrent page faults so it is better than munmap if it's not lack of address space. But a problem of this approach is that most of allocator reuses freed memory space soonish so users of allocator will see page fault, page allocation, page zeroing if allocator already called MADV_DONNEED on the address space.

For avoidng that overheads, other OS have supported MADV_FREE. The idea is just clear dirty bit from pte when the syscall is called and purge them if memory pressure happens.

If there is write(ie, store) operation in MADV_FREEed page, VM checks pte_dirty and don't purge the page so users could the page without any corruption.

For testing, I tweaked jamalloc to use MADV_FREE.

src/chunk_mmap.c +1 -1
index 8a42e75..20e31af 100644
@@ -131,7 +131,7 @@ pages_purge(void *addr, size_t length)
 #  else
 #    error "No method defined for purging unused dirty pages."
 #  endif
-       int err = madvise(addr, length, JEMALLOC_MADV_PURGE);
+       int err = madvise(addr, length, 5);
        unzeroed = (JEMALLOC_MADV_ZEROS == false || err != 0);
 #  undef JEMALLOC_MADV_PURGE
 #  undef JEMALLOC_MADV_ZEROS

RAM 2G, CPU 4, ebizzy benchmark(./ebizzy -S 30 -n 512)

(1.1) stands for 1 process and 1 thread so for exmaple, (1.4) means 1 process and 4 thread.

vanilla jemalloc patched jemalloc

1.1       1.1
records:  5               records:  5
avg:      7417.80         avg:      13866.00
std:      82.53(1.11%)    std:      323.16(2.33%)
max:      7559.00         max:      14264.00
min:      7309.00         min:      13543.00
1.4       1.4
records:  5               records:  5
avg:      16353.80        avg:      30380.00
std:      423.30(2.59%)   std:      852.16(2.81%)
max:      16823.00        max:      31819.00
min:      15788.00        min:      29310.00
1.8       1.8
records:  5               records:  5
avg:      15766.00        avg:      27498.40
std:      1073.76(6.81%)  std:      1838.82(6.69%)
max:      17259.00        max:      30070.00
min:      13919.00        min:      24810.00
4.1       4.1
records:  5               records:  5
avg:      4000.40         avg:      7926.60
std:      9.75(0.24%)     std:      126.84(1.60%)
max:      4013.00         max:      8171.00
min:      3984.00         min:      7805.00
4.4       4.4
records:  5               records:  5
avg:      3920.40         avg:      7046.80
std:      73.11(1.86%)    std:      148.47(2.11%)
max:      4044.00         max:      7320.00
min:      3838.00         min:      6876.00
4.8       4.8
records:  5               records:  5
avg:      3951.80         avg:      7024.60
std:      51.47(1.30%)    std:      150.76(2.15%)
max:      4048.00         max:      7284.00
min:      3893.00         min:      6814.00
8.1       8.1
records:  5               records:  5
avg:      1919.80         avg:      3354.00
std:      39.33(2.05%)    std:      100.58(3.00%)
max:      1989.00         max:      3529.00
min:      1870.00         min:      3227.00
8.4       8.4
records:  5               records:  5
avg:      1946.60         avg:      2800.40
std:      22.69(1.17%)    std:      246.64(8.81%)
max:      1977.00         max:      3081.00
min:      1915.00         min:      2394.00
8.8       8.8
records:  5               records:  5
avg:      1947.20         avg:      2249.60
std:      19.54(1.00%)    std:      131.43(5.84%)
max:      1973.00         max:      2505.00
min:      1929.00         min:      2149.00

MADV_FREE is about 2 time faster than MADV_DONTNEED but it starts slow down as memory pressure is heavy compared to DONTNEED. It's natural because MADV_FREE needs more steps to free pages so one thing I have a mind to overcome is just purge them if memory pressure is severe(ex, kswapd active) rather than giving a chance to promote freeing page from inactive LRU when madvise_free is called.

(just wondering, when I used PG_lazyfree(ie, PG_private)
in previous internal version, I didn't see the above regression in same test. I will investigate what's culprit in there.)

Still, I didn't test a lot and surely needs more description and a few TODO(ex, lazyfree page accoutning and work with swapless system but it's further enhance, not necessary in this stage) but it's enough to show the concept and direction before LSF/MM.

Patchset is based on 3.14-rc6.

Welcome any comment!

* From v1
* Use custom page table walker for madvise_free - Johannes
* Remove PG_lazypage flag - Johannes
* Do madvise_dontneed instead of madvise_freein swapless system

Minchan Kim (3):
mm: support madvise(MADV_FREE)
mm: work deactivate_page with anon pages
mm: deactivate lazyfree pages

 include/linux/mm.h                     |   2 +
 include/linux/mm_inline.h              |   9 ++
 include/linux/rmap.h                   |   6 ++
 include/linux/vm_event_item.h          |   1 +
 include/uapi/asm-generic/mman-common.h |   1 +
 mm/madvise.c                           |  25 +++++
 mm/memory.c                            | 162 ++++++++++++++++++++++++++++++++-
 mm/rmap.c                              |  31 ++++++-
 mm/swap.c                              |  20 ++--
 mm/swap_state.c                        |   3 +-
 mm/vmscan.c                            |  12 +++
 mm/vmstat.c                            |   1 +
 12 files changed, 255 insertions(+), 18 deletions(-)
Signature · 1 line
1.9.0
#1 lore raw

RFC v2 1/3 mm: support madvise(MADV_FREE)

Linux doesn't have an ability to free pages lazy while other OS already have been supported that named by madvise(MADV_FREE).

The gain is clear that kernel can evict freed pages rather than swapping out or OOM if memory pressure happens.

Without memory pressure, freed pages would be reused by userspace without another additional overhead(ex, page fault + allocation
+ zeroing).

Firstly, heavy users would be general allocators(ex, jemalloc, I hope ptmalloc support it) and jemalloc already have supported the feature for other OS(ex, FreeBSD)

Quoted 1 line

From comment from Johannes, I removed PG_lazyflag in patchset

and just check pte dirtiness but the problem of the approach is the page readed by swap-read. It couldn't set a dirty bit to pte but it shouldn't be discarded so I decide making pte dirty on every read-in page. Surely, it would make CRIU guys unhappy because it would make more diff data for them so Ccing them with hope that they might have an idea.

We could use SetPageDirty in do_swap_page forcefully and check it in try_to_unmap_one to detect lazyfree as well as pte dirtiness but it would cause unnecessary swapout although same page was already wroten to swap device. :(

If there isn't great idea, I should roll back to PG_lazyfree approach.

Cc Cyrill Gorcunov <[email protected]>
Cc Pavel Emelyanov <[email protected]>
Cc Hugh Dickins <[email protected]>
Cc Johannes Weiner <[email protected]>
Cc Rik van Riel <[email protected]>
---
 include/linux/mm.h                     |   2 +
 include/linux/rmap.h                   |   6 ++
 include/linux/vm_event_item.h          |   1 +
 include/uapi/asm-generic/mman-common.h |   1 +
 mm/madvise.c                           |  25 +++++
 mm/memory.c                            | 161 ++++++++++++++++++++++++++++++++-
 mm/rmap.c                              |  31 ++++++-
 mm/swap_state.c                        |   3 +-
 mm/vmscan.c                            |  12 +++
 mm/vmstat.c                            |   1 +
 10 files changed, 235 insertions(+), 8 deletions(-)
include/linux/mm.h +2 -0
index c1b7414c7bef..79af90212c19 100644
@@ -1063,6 +1063,8 @@ int zap_vma_ptes(struct vm_area_struct *vma, unsigned long address,
 		unsigned long size);
 void zap_page_range(struct vm_area_struct *vma, unsigned long address,
 		unsigned long size, struct zap_details *);
+void lazyfree_range(struct vm_area_struct *vma, unsigned long address,
+		unsigned long size);
 void unmap_vmas(struct mmu_gather *tlb, struct vm_area_struct *start_vma,
 		unsigned long start, unsigned long end);
 
include/linux/rmap.h +6 -0
index 1da693d51255..edd6aef92c0f 100644
@@ -235,6 +235,11 @@ struct anon_vma *page_lock_anon_vma_read(struct page *page);
 void page_unlock_anon_vma_read(struct anon_vma *anon_vma);
 int page_mapped_in_vma(struct page *page, struct vm_area_struct *vma);
 
+struct rmap_private {
+	enum ttu_flags flags;
+	int dirtied;	/* used for lazyfree */
+};
+
 /*
  * rmap_walk_control: To control rmap traversing for specific needs
  *
@@ -289,5 +294,6 @@ static inline int page_mkclean(struct page *page)
 #define SWAP_AGAIN	1
 #define SWAP_FAIL	2
 #define SWAP_MLOCK	3
+#define SWAP_DISCARD	4
 
 #endif	/* _LINUX_RMAP_H */
include/linux/vm_event_item.h +1 -0
index 3a712e2e7d76..a69680d335bb 100644
@@ -25,6 +25,7 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT,
 		FOR_ALL_ZONES(PGALLOC),
 		PGFREE, PGACTIVATE, PGDEACTIVATE,
 		PGFAULT, PGMAJFAULT,
+		PGLAZYFREED,
 		FOR_ALL_ZONES(PGREFILL),
 		FOR_ALL_ZONES(PGSTEAL_KSWAPD),
 		FOR_ALL_ZONES(PGSTEAL_DIRECT),
include/uapi/asm-generic/mman-common.h +1 -0
index 4164529a94f9..7e257e49be2e 100644
@@ -34,6 +34,7 @@
 #define MADV_SEQUENTIAL	2		/* expect sequential page references */
 #define MADV_WILLNEED	3		/* will need these pages */
 #define MADV_DONTNEED	4		/* don't need these pages */
+#define MADV_FREE	5		/* do lazy free */
 
 /* common parameters: try to keep these consistent across architectures */
 #define MADV_REMOVE	9		/* remove these pages & resources */
mm/madvise.c +25 -0
index 539eeb96b323..3e4568ac02f0 100644
@@ -31,6 +31,7 @@ static int madvise_need_mmap_write(int behavior)
 	case MADV_REMOVE:
 	case MADV_WILLNEED:
 	case MADV_DONTNEED:
+	case MADV_FREE:
 		return 0;
 	default:
 		/* be safe, default to 1. list exceptions explicitly */
@@ -251,6 +252,22 @@ static long madvise_willneed(struct vm_area_struct *vma,
 	return 0;
 }
 
+static long madvise_free(struct vm_area_struct *vma,
+			     struct vm_area_struct **prev,
+			     unsigned long start, unsigned long end)
+{
+	*prev = vma;
+	if (vma->vm_flags & (VM_LOCKED|VM_HUGETLB|VM_PFNMAP))
+		return -EINVAL;
+
+	/* madv_free works for only anon vma */
+	if (vma->vm_file)
+		return -EINVAL;
+
+	lazyfree_range(vma, start, end - start);
+	return 0;
+}
+
 /*
  * Application no longer needs these pages.  If the pages are dirty,
  * it's OK to just throw them away.  The app will be more careful about
@@ -384,6 +401,13 @@ madvise_vma(struct vm_area_struct *vma, struct vm_area_struct **prev,
 		return madvise_remove(vma, prev, start, end);
 	case MADV_WILLNEED:
 		return madvise_willneed(vma, prev, start, end);
+	case MADV_FREE:
+		/*
+		 * At the moment, MADV_FREE doesn't support swapless
+		 * Will revisit.
+		 */
+		if (get_nr_swap_pages() > 0)
+			return madvise_free(vma, prev, start, end);
 	case MADV_DONTNEED:
 		return madvise_dontneed(vma, prev, start, end);
 	default:
@@ -403,6 +427,7 @@ madvise_behavior_valid(int behavior)
 	case MADV_REMOVE:
 	case MADV_WILLNEED:
 	case MADV_DONTNEED:
+	case MADV_FREE:
 #ifdef CONFIG_KSM
 	case MADV_MERGEABLE:
 	case MADV_UNMERGEABLE:
mm/memory.c +159 -2

191 lines not on this page · raw message

mm/rmap.c +27 -4
index d9d42316a99a..83b04c437f00 100644
@@ -1120,7 +1120,8 @@ int try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
 	pte_t pteval;
 	spinlock_t *ptl;
 	int ret = SWAP_AGAIN;
-	enum ttu_flags flags = (enum ttu_flags)arg;
+	struct rmap_private *rp = (struct rmap_private *)arg;
+	enum ttu_flags flags = rp->flags;
 
 	pte = page_check_address(page, mm, address, &ptl, 0);
 	if (!pte)
@@ -1169,6 +1170,13 @@ int try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
 		swp_entry_t entry = { .val = page_private(page) };
 		pte_t swp_pte;
 
+		/* discard lazyfree page if there was no write access */
+		if (!pte_dirty(pteval)) {
+			dec_mm_counter(mm, MM_ANONPAGES);
+			goto discard;
+		}
+
+		rp->dirtied++;
 		if (PageSwapCache(page)) {
 			/*
 			 * Store the swap location in the pte.
@@ -1210,6 +1218,7 @@ int try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
 	} else
 		dec_mm_counter(mm, MM_FILEPAGES);
 
+discard:
 	page_remove_rmap(page);
 	page_cache_release(page);
 
@@ -1469,13 +1478,19 @@ static int page_not_mapped(struct page *page)
  * SWAP_AGAIN	- we missed a mapping, try again later
  * SWAP_FAIL	- the page is unswappable
  * SWAP_MLOCK	- page is mlocked.
+ * SWAP_DISCARD - we succedded but no need to swap out
  */
 int try_to_unmap(struct page *page, enum ttu_flags flags)
 {
 	int ret;
+
+	struct rmap_private rp = {
+		.flags = flags,
+	};
+
 	struct rmap_walk_control rwc = {
 		.rmap_one = try_to_unmap_one,
-		.arg = (void *)flags,
+		.arg = &rp,
 		.done = page_not_mapped,
 		.file_nonlinear = try_to_unmap_nonlinear,
 		.anon_lock = page_lock_anon_vma_read,
@@ -1496,8 +1511,12 @@ int try_to_unmap(struct page *page, enum ttu_flags flags)
 
 	ret = rmap_walk(page, &rwc);
 
-	if (ret != SWAP_MLOCK && !page_mapped(page))
+	if (ret != SWAP_MLOCK && !page_mapped(page)) {
 		ret = SWAP_SUCCESS;
+		if (PageAnon(page) && !rp.dirtied)
+			ret = SWAP_DISCARD;
+	}
+
 	return ret;
 }
 
@@ -1519,9 +1538,13 @@ int try_to_unmap(struct page *page, enum ttu_flags flags)
 int try_to_munlock(struct page *page)
 {
 	int ret;
+	struct rmap_private rp = {
+		.flags = TTU_MUNLOCK,
+	};
+
 	struct rmap_walk_control rwc = {
 		.rmap_one = try_to_unmap_one,
-		.arg = (void *)TTU_MUNLOCK,
+		.arg = &rp,
 		.done = page_not_mapped,
 		/*
 		 * We don't bother to try to find the munlocked page in
mm/swap_state.c +1 -2
index e76ace30d436..7ddc59913952 100644
@@ -189,13 +189,12 @@ int add_to_swap(struct page *page, struct list_head *list)
 	 * deadlock in the swap out path.
 	 */
 	/*
-	 * Add it to the swap cache and mark it dirty
+	 * Add it to the swap cache.
 	 */
 	err = add_to_swap_cache(page, entry,
 			__GFP_HIGH|__GFP_NOMEMALLOC|__GFP_NOWARN);
 
 	if (!err) {	/* Success */
-		SetPageDirty(page);
 		return 1;
 	} else {	/* -ENOMEM radix-tree allocation failure */
 		/*
mm/vmscan.c +12 -0
index a9c74b409681..986c06b85808 100644
@@ -803,6 +803,7 @@ static unsigned long shrink_page_list(struct list_head *page_list,
 		int may_enter_fs;
 		enum page_references references = PAGEREF_RECLAIM_CLEAN;
 		bool dirty, writeback;
+		bool lazyfree = false;
 
 		cond_resched();
 
@@ -944,6 +945,7 @@ static unsigned long shrink_page_list(struct list_head *page_list,
 		if (PageAnon(page) && !PageSwapCache(page)) {
 			if (!(sc->gfp_mask & __GFP_IO))
 				goto keep_locked;
+			/* try_to_unmap will set dirty flag */
 			if (!add_to_swap(page, page_list))
 				goto activate_locked;
 			may_enter_fs = 1;
@@ -964,6 +966,14 @@ static unsigned long shrink_page_list(struct list_head *page_list,
 				goto keep_locked;
 			case SWAP_MLOCK:
 				goto cull_mlocked;
+			case SWAP_DISCARD:
+				if (PageSwapCache(page))
+					try_to_free_swap(page);
+				if (!page_freeze_refs(page, 1))
+					goto keep_locked;
+				__clear_page_locked(page);
+				count_vm_event(PGLAZYFREED);
+				goto free_it;
 			case SWAP_SUCCESS:
 				; /* try to free the page below */
 			}
@@ -1078,6 +1088,8 @@ static unsigned long shrink_page_list(struct list_head *page_list,
 		__clear_page_locked(page);
 free_it:
 		nr_reclaimed++;
+		if (lazyfree)
+			count_vm_event(PGLAZYFREED);
 
 		/*
 		 * Is there need to periodically free_page_list? It would
mm/vmstat.c +1 -0
index def5dd2fbe61..2d80f7ed495d 100644
@@ -789,6 +789,7 @@ const char * const vmstat_text[] = {
 
 	"pgfault",
 	"pgmajfault",
+	"pglazyfreed",
 
 	TEXTS_FOR_ZONES("pgrefill")
 	TEXTS_FOR_ZONES("pgsteal_kswapd")
Signature · 7 lines
1.9.0

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#2 lore raw

RFC v2 2/3 mm: work deactivate_page with anon pages

In reply to Minchan Kim

Now, deactivate_page works for file page but MADV_FREE will use it to move lazyfree pages to inactive LRU's tail so this patch makes deactivate_page work with anon pages as well as file pages.

---
 include/linux/mm_inline.h |  9 +++++++++
 mm/swap.c                 | 20 ++++++++++----------
 2 files changed, 19 insertions(+), 10 deletions(-)
include/linux/mm_inline.h +9 -0
index cf55945c83fb..0503caafd532 100644
@@ -22,6 +22,15 @@ static inline int page_is_file_cache(struct page *page)
 	return !PageSwapBacked(page);
 }
 
+static __always_inline void add_page_to_lru_list_tail(struct page *page,
+				struct lruvec *lruvec, enum lru_list lru)
+{
+	int nr_pages = hpage_nr_pages(page);
+	mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
+	list_add_tail(&page->lru, &lruvec->lists[lru]);
+	__mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, nr_pages);
+}
+
 static __always_inline void add_page_to_lru_list(struct page *page,
 				struct lruvec *lruvec, enum lru_list lru)
 {
mm/swap.c +10 -10
index 0092097b3f4c..ac13714b5d8b 100644
@@ -643,14 +643,11 @@ void add_page_to_unevictable_list(struct page *page)
  * If the page isn't page_mapped and dirty/writeback, the page
  * could reclaim asap using PG_reclaim.
  *
- * 1. active, mapped page -> none
- * 2. active, dirty/writeback page -> inactive, head, PG_reclaim
- * 3. inactive, mapped page -> none
- * 4. inactive, dirty/writeback page -> inactive, head, PG_reclaim
- * 5. inactive, clean -> inactive, tail
- * 6. Others -> none
+ * 1. file mapped page -> none
+ * 2. dirty/writeback page -> head of inactive with PG_reclaim
+ * 3. inactive, clean -> tail of inactive
  *
- * In 4, why it moves inactive's head, the VM expects the page would
+ * In 2, why it moves inactive's head, the VM expects the page would
  * be write it out by flusher threads as this is much more effective
  * than the single-page writeout from reclaim.
  */
@@ -667,7 +664,7 @@ static void lru_deactivate_fn(struct page *page, struct lruvec *lruvec,
 		return;
 
 	/* Some processes are using the page */
-	if (page_mapped(page))
+	if (!PageAnon(page) && page_mapped(page))
 		return;
 
 	active = PageActive(page);
@@ -677,7 +674,6 @@ static void lru_deactivate_fn(struct page *page, struct lruvec *lruvec,
 	del_page_from_lru_list(page, lruvec, lru + active);
 	ClearPageActive(page);
 	ClearPageReferenced(page);
-	add_page_to_lru_list(page, lruvec, lru);
 
 	if (PageWriteback(page) || PageDirty(page)) {
 		/*
@@ -686,12 +682,16 @@ static void lru_deactivate_fn(struct page *page, struct lruvec *lruvec,
 		 * is _really_ small and  it's non-critical problem.
 		 */
 		SetPageReclaim(page);
+		add_page_to_lru_list(page, lruvec, lru);
 	} else {
 		/*
 		 * The page's writeback ends up during pagevec
 		 * We moves tha page into tail of inactive.
+		 *
+		 * The lazyfree page move into lru's tail to
+		 * discard easily.
 		 */
-		list_move_tail(&page->lru, &lruvec->lists[lru]);
+		add_page_to_lru_list_tail(page, lruvec, lru);
 		__count_vm_event(PGROTATED);
 	}
 
Signature · 7 lines
1.9.0

--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to [email protected].  For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
#3 lore raw

RFC v2 3/3 mm: deactivate lazyfree pages

In reply to Minchan Kim

MADV_FREEed pages should be discarded before working set pages are reclaimed because most of users(ex, tcmalloc and jemalloc) have used it instead of MADV_DONTNEED which zap pages instantly.

---
 mm/memory.c | 1 +
 1 file changed, 1 insertion(+)
mm/memory.c +1 -0
index 6f221225f62b..76b683e7d087 100644
@@ -1298,6 +1298,7 @@ static unsigned long lazyfree_pte_range(struct mmu_gather *tlb,
 		ptent = pte_mkclean(ptent);
 		set_pte_at(mm, addr, pte, ptent);
 		tlb_remove_tlb_entry(tlb, pte, addr);
+		deactivate_page(page);
 	} while (pte++, addr += PAGE_SIZE, addr != end);
 	arch_leave_lazy_mmu_mode();
 	pte_unmap_unlock(start_pte, ptl);
Signature · 1 line
1.9.0