lkml.org 
[lkml]   [2001]   [Sep]   [25]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[PATCH] Re: 2.4.9-ac15 painfully sluggish
On Tue, 25 Sep 2001, Pau Aliagas wrote:

> The problem seems to be related in pages not moved to swap but
> being discarded somehow and reread later on.... just a guess.

I've made a small patch to 2.4.9-ac15 which should make
page_launder() smoother, make some (very minor) tweaks
to page aging and updates various comments in vmscan.c

It's below this email and at:

http://www.surriel.com/patches/2.4/2.4.9-ac15-age+launder

Since I failed to break 2.4.9-ac15 with this patch following
the instructions given to me by others, it would be nice to
know if the thing can still break on your machines.

Please test,

Rik
--
IA64: a worthy successor to the i860.

http://www.surriel.com/
http://www.conectiva.com/ http://distro.conectiva.com/




--- linux-2.4.9-ac15/mm/vmscan.c.orig Mon Sep 24 17:30:48 2001
+++ linux-2.4.9-ac15/mm/vmscan.c Tue Sep 25 13:33:16 2001
@@ -28,19 +28,12 @@

static inline void age_page_up(struct page *page)
{
- unsigned long age = page->age;
- age += PAGE_AGE_ADV;
- if (age > PAGE_AGE_MAX)
- age = PAGE_AGE_MAX;
- page->age = age;
+ page->age = min((int) (page->age + PAGE_AGE_ADV), PAGE_AGE_MAX);
}

static inline void age_page_down(struct page *page)
{
- unsigned long age = page->age;
- if (age > 0)
- age -= PAGE_AGE_DECL;
- page->age = age;
+ page->age -= min(PAGE_AGE_DECL, (int)page->age);
}

/*
@@ -108,6 +101,23 @@
pte_t pte;
swp_entry_t entry;

+ /* Don't look at this pte if it's been accessed recently. */
+ if (ptep_test_and_clear_young(page_table)) {
+ age_page_up(page);
+ return;
+ }
+
+ /*
+ * If the page is on the active list, page aging is done in
+ * refill_inactive_scan(), anonymous pages are aged here.
+ * This is done so heavily shared pages (think libc.so)
+ * don't get punished heavily while they are still in use.
+ * The alternative would be to put anonymous pages on the
+ * active list too, but that increases complexity (for now).
+ */
+ if (!PageActive(page))
+ age_page_down(page);
+
/*
* If we have plenty inactive pages on this
* zone, skip it.
@@ -133,23 +143,6 @@
pte = ptep_get_and_clear(page_table);
flush_tlb_page(vma, address);

- /* Don't look at this pte if it's been accessed recently. */
- if (ptep_test_and_clear_young(page_table)) {
- age_page_up(page);
- return;
- }
-
- /*
- * If the page is on the active list, page aging is done in
- * refill_inactive_scan(), anonymous pages are aged here.
- * This is done so heavily shared pages (think libc.so)
- * don't get punished heavily while they are still in use.
- * The alternative would be to put anonymous pages on the
- * active list too, but that increases complexity (for now).
- */
- if (!PageActive(page))
- age_page_down(page);
-
/*
* Is the page already in the swap cache? If so, then
* we can just drop our reference to it without doing
@@ -447,6 +440,7 @@
page_count(page) > (1 + !!page->buffers)) {
del_page_from_inactive_clean_list(page);
add_page_to_active_list(page);
+ page->age = max((int)page->age, PAGE_AGE_START);
continue;
}

@@ -536,34 +530,56 @@
* @gfp_mask: what operations we are allowed to do
* @sync: are we allowed to do synchronous IO in emergencies ?
*
- * When this function is called, we are most likely low on free +
- * inactive_clean pages. Since we want to refill those pages as
- * soon as possible, we'll make two loops over the inactive list,
- * one to move the already cleaned pages to the inactive_clean lists
- * and one to (often asynchronously) clean the dirty inactive pages.
+ * This function is called when we are low on free / inactive_clean
+ * pages, its purpose is to refill the free/clean list as efficiently
+ * as possible.
*
- * In situations where kswapd cannot keep up, user processes will
- * end up calling this function. Since the user process needs to
- * have a page before it can continue with its allocation, we'll
- * do synchronous page flushing in that case.
+ * This means we do writes asynchronously as long as possible and will
+ * only sleep on IO when we don't have another option. Since writeouts
+ * cause disk seeks and make read IO slower, we skip writes alltogether
+ * when the amount of dirty pages is small.
*
* This code is heavily inspired by the FreeBSD source code. Thanks
* go out to Matthew Dillon.
*/
-#define MAX_LAUNDER (4 * (1 << page_cluster))
-#define CAN_DO_FS (gfp_mask & __GFP_FS)
-#define CAN_DO_IO (gfp_mask & __GFP_IO)
+#define CAN_DO_FS ((gfp_mask & __GFP_FS) && should_write)
+#define WRITE_LOW_WATER 5
+#define WRITE_HIGH_WATER 10
int page_launder(int gfp_mask, int sync)
{
- int launder_loop, maxscan, cleaned_pages, maxlaunder;
+ int maxscan, cleaned_pages, failed_pages, total;
struct list_head * page_lru;
struct page * page;

- launder_loop = 0;
- maxlaunder = 0;
+ /*
+ * This flag determines if we should do writeouts of dirty data
+ * or not. When more than WRITE_HIGH_WATER percentage of the
+ * pages we process would need to be written out we set this flag
+ * and will do writeout, the flag is cleared once we go below
+ * WRITE_LOW_WATER. Note that only pages we actually process
+ * get counted, ie. pages where we make it beyond the TryLock.
+ *
+ * XXX: These flags still need tuning.
+ */
+ static int should_write = 0;
+
cleaned_pages = 0;
+ failed_pages = 0;
+
+ /*
+ * The gfp_mask tells try_to_free_buffers() below if it should
+ * wait do IO or may be allowed to wait on IO synchronously.
+ *
+ * Note that syncronous IO only happens when a page has not been
+ * written out yet when we see it for a second time, this is done
+ * through magic in try_to_free_buffers().
+ */
+ if (!should_write)
+ gfp_mask &= ~(__GFP_WAIT | __GFP_IO);
+ else if (!sync)
+ gfp_mask &= ~__GFP_WAIT;

-dirty_page_rescan:
+ /* The main launder loop. */
spin_lock(&pagemap_lru_lock);
maxscan = nr_inactive_dirty_pages;
while ((page_lru = inactive_dirty_list.prev) != &inactive_dirty_list &&
@@ -580,26 +596,36 @@
}

/*
- * If the page is or was in use, we move it to the active
- * list. Note that pages in use by processes can easily
- * end up here to be unmapped later, but we just don't
- * want them clogging up the inactive list.
+ * The page is in active use or really unfreeable. Move to
+ * the active list and adjust the page age if needed.
*/
- if ((PageReferenced(page) || page->age > 0 ||
- page_count(page) > (1 + !!page->buffers) ||
- page_ramdisk(page)) && !PageLocked(page)) {
+ if (PageReferenced(page) || page->age || page_ramdisk(page)) {
del_page_from_inactive_dirty_list(page);
add_page_to_active_list(page);
+ page->age = max((int)page->age, PAGE_AGE_START);
continue;
}

/*
- * If we have plenty free pages on a zone:
+ * The page is still in the page tables of some process,
+ * move it to the active list but leave page age at 0;
+ * either swap_out() will make it freeable soon or it is
+ * mlock()ed...
*
- * 1) we avoid a writeout for that page if its dirty.
- * 2) if its a buffercache page, and not a pagecache
- * one, we skip it since we cannot move it to the
- * inactive clean list --- we have to free it.
+ * The !PageLocked() test is to protect us from ourselves,
+ * see the code around the writepage() call.
+ */
+ if ((page_count(page) > (1 + !!page->buffers)) &&
+ !PageLocked(page)) {
+ del_page_from_inactive_dirty_list(page);
+ add_page_to_active_list(page);
+ continue;
+ }
+
+ /*
+ * If this zone has plenty of pages free, don't spend time
+ * on cleaning it but only move clean pages out of the way
+ * so we won't have to scan those again.
*/
if (zone_free_plenty(page->zone)) {
if (!page->mapping || page_dirty(page)) {
@@ -633,11 +659,12 @@
if (!writepage)
goto page_active;

- /* First time through? Move it to the back of the list */
- if (!launder_loop || !CAN_DO_FS) {
+ /* Can't do it? Move it to the back of the list. */
+ if (!CAN_DO_FS) {
list_del(page_lru);
list_add(page_lru, &inactive_dirty_list);
UnlockPage(page);
+ failed_pages++;
continue;
}

@@ -664,9 +691,7 @@
* buffer pages
*/
if (page->buffers) {
- unsigned int buffer_mask;
int clearedbuf;
- int freed_page = 0;
/*
* Since we might be doing disk IO, we have to
* drop the spinlock and take an extra reference
@@ -676,16 +701,8 @@
page_cache_get(page);
spin_unlock(&pagemap_lru_lock);

- /* Will we do (asynchronous) IO? */
- if (launder_loop && maxlaunder == 0 && sync)
- buffer_mask = gfp_mask; /* Do as much as we can */
- else if (launder_loop && maxlaunder-- > 0)
- buffer_mask = gfp_mask & ~__GFP_WAIT; /* Don't wait, async write-out */
- else
- buffer_mask = gfp_mask & ~(__GFP_WAIT | __GFP_IO); /* Don't even start IO */
-
- /* Try to free metadata underlying the page. */
- clearedbuf = try_to_release_page(page, buffer_mask);
+ /* Try to free the page buffers. */
+ clearedbuf = try_to_release_page(page, gfp_mask);

/*
* Re-take the spinlock. Note that we cannot
@@ -697,11 +714,11 @@
/* The buffers were not freed. */
if (!clearedbuf) {
add_page_to_inactive_dirty_list(page);
+ failed_pages++;

/* The page was only in the buffer cache. */
} else if (!page->mapping) {
atomic_dec(&buffermem_pages);
- freed_page = 1;
cleaned_pages++;

/* The page has more users besides the cache and us. */
@@ -722,13 +739,6 @@
UnlockPage(page);
page_cache_release(page);

- /*
- * If we're freeing buffer cache pages, stop when
- * we've got enough free memory.
- */
- if (freed_page && !free_shortage())
- break;
-
continue;
} else if (page->mapping && !PageDirty(page)) {
/*
@@ -750,33 +760,22 @@
*/
del_page_from_inactive_dirty_list(page);
add_page_to_active_list(page);
+ page->age = max((int)page->age, PAGE_AGE_START);
UnlockPage(page);
}
}
spin_unlock(&pagemap_lru_lock);

/*
- * If we don't have enough free pages, we loop back once
- * to queue the dirty pages for writeout. When we were called
- * by a user process (that /needs/ a free page) and we didn't
- * free anything yet, we wait synchronously on the writeout of
- * MAX_SYNC_LAUNDER pages.
- *
- * We also wake up bdflush, since bdflush should, under most
- * loads, flush out the dirty pages before we have to wait on
- * IO.
+ * Set the should_write flag, for the next callers of page_launder.
+ * If we go below the low watermark we stop the writeout of dirty
+ * pages, writeout is started when we get above the high watermark.
*/
- if (CAN_DO_IO && !launder_loop && free_shortage()) {
- launder_loop = 1;
- /* If we cleaned pages, never do synchronous IO. */
- if (cleaned_pages)
- sync = 0;
- /* We only do a few "out of order" flushes. */
- maxlaunder = MAX_LAUNDER;
- /* Kflushd takes care of the rest. */
- wakeup_bdflush(0);
- goto dirty_page_rescan;
- }
+ total = failed_pages + cleaned_pages;
+ if (should_write && failed_pages * 100 < WRITE_LOW_WATER * total)
+ should_write = 0;
+ else if (!should_write && failed_pages * 100 > WRITE_HIGH_WATER * total)
+ should_write = 1;

/* Return the number of pages moved to the inactive_clean list. */
return cleaned_pages;
@@ -998,27 +997,26 @@
return progress;
}

-
-
+/*
+ * Worker function for kswapd and try_to_free_pages, we get
+ * called whenever there is a shortage of free/inactive_clean
+ * pages.
+ *
+ * This function will also move pages to the inactive list,
+ * if needed.
+ */
static int do_try_to_free_pages(unsigned int gfp_mask, int user)
{
int ret = 0;

/*
- * If we're low on free pages, move pages from the
- * inactive_dirty list to the inactive_clean list.
- *
- * Usually bdflush will have pre-cleaned the pages
- * before we get around to moving them to the other
- * list, so this is a relatively cheap operation.
+ * Eat memory from filesystem page cache, buffer cache,
+ * dentry, inode and filesystem quota caches.
*/
-
- if (free_shortage()) {
- ret += page_launder(gfp_mask, user);
- shrink_dcache_memory(0, gfp_mask);
- shrink_icache_memory(0, gfp_mask);
- shrink_dqcache_memory(DEF_PRIORITY, gfp_mask);
- }
+ ret += page_launder(gfp_mask, user);
+ shrink_dcache_memory(0, gfp_mask);
+ shrink_icache_memory(0, gfp_mask);
+ shrink_dqcache_memory(DEF_PRIORITY, gfp_mask);

/*
* If needed, we move pages from the active list
@@ -1028,7 +1026,7 @@
ret += refill_inactive(gfp_mask);

/*
- * Reclaim unused slab cache if memory is low.
+ * Reclaim unused slab cache memory.
*/
kmem_cache_reap(gfp_mask);

@@ -1080,7 +1078,7 @@
static long recalc = 0;

/* If needed, try to free some memory. */
- if (inactive_shortage() || free_shortage())
+ if (free_shortage())
do_try_to_free_pages(GFP_KSWAPD, 0);

/* Once a second ... */
@@ -1090,8 +1088,6 @@
/* Do background page aging. */
refill_inactive_scan(DEF_PRIORITY);
}
-
- run_task_queue(&tq_disk);

/*
* We go to sleep if either the free page shortage
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

\
 
 \ /
  Last update: 2005-03-22 13:03    [W:0.067 / U:8.496 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site