perzone slab LRUs
Subsystems (6)
A message that is not in the archive (sent off-list or not yet received): [email protected]
RFC PATCH 2/2 perzone slab LRUs
Oops, forgot to CC linux-mm.
Quoted 5 lines · Nick Piggin
This patch is only intended for comments.
This implements (crappy?) infrastructure for per-zone slab LRUs for reclaimable slabs, and moves
dcache.cover to use that.
Quoted 8 lines
The global unused list is retained to reduce intrusiveness, and another per-zone LRU list is added (which are still protected with the global dcache lock). This is an attempt to make slab scanning more robust on highmem and NUMA systems.
One concern is that off-zone dentries might be pinning inodes in the zone we're trying to free memory for. I wonder if this can be solved?
---
linux-2.6-npiggin/fs/dcache.c | 120 ++++++++++++++++++++------ linux-2.6-npiggin/include/linux/dcache.h | 1 linux-2.6-npiggin/include/linux/mm.h | 19 ++++ linux-2.6-npiggin/include/linux/mmzone.h | 4 linux-2.6-npiggin/mm/page_alloc.c | 1 linux-2.6-npiggin/mm/vmscan.c | 139 ++++++++++++++++++++++++------- 6 files changed, 227 insertions(+), 57 deletions(-)
fs/dcache.c renamed from fs/dcache.c~perzone-slab +93 -27
@@ -60,6 +60,7 @@ static unsigned int d_hash_mask;static unsigned int d_hash_shift;static struct hlist_head *dentry_hashtable;static LIST_HEAD (dentry_unused); +static int zone_shrinker;/* Statistics gathering. */ struct dentry_stat_t dentry_stat = {
@@ -86,6 +87,22 @@ static void d_free(struct dentry *dentrycall_rcu (&dentry->d_rcu, d_callback); } +static void dentry_add_lru (struct dentry *dentry) +{ +struct zone_shrinker *zs; + zs =get_zone_shrinker (page_zone (virt_to_page (dentry)), zone_shrinker); +list_add (&dentry->d_lru, &zs->lru); + zs->nr++; +} + +static void dentry_del_lru (struct dentry *dentry) +{ +struct zone_shrinker *zs; + zs =get_zone_shrinker (page_zone (virt_to_page (dentry)), zone_shrinker); +list_del (&dentry->d_lru); + zs->nr--; +} +/* * Release the dentry's inode, using the filesystem * d_iput() operation if defined.
@@ -153,7 +170,7 @@ repeat:spin_unlock (&dcache_lock);return ; } - +/* * AV: ->d_delete() is _NOT_ allowed to block now. */
@@ -164,9 +181,9 @@ repeat:/* Unreachable? Get rid of it */ if (d_unhashed (dentry)) goto kill_it; -if (list_empty (&dentry->d_lru)) { - dentry->d_flags |=DCACHE_REFERENCED ; -list_add (&dentry->d_lru, &dentry_unused); + dentry->d_flags |=DCACHE_REFERENCED ; +if (list_empty (&dentry->d_unused)) { +list_add (&dentry->d_unused, &dentry_unused); dentry_stat.nr_unused++; }spin_unlock (&dentry->d_lock);
@@ -179,11 +196,12 @@ unhash_it: kill_it: {struct dentry *parent; -/* If dentry was on d_lru list +/* If dentry was on d_unused list * delete it from there */ -if (!list_empty (&dentry->d_lru)) { -list_del (&dentry->d_lru); +dentry_del_lru (dentry); +if (!list_empty (&dentry->d_unused)) { +list_del (&dentry->d_unused); dentry_stat.nr_unused--; }list_del (&dentry->d_child);
@@ -261,9 +279,9 @@ int d_invalidate(struct dentry * dentry)static inline struct dentry *__dget_locked (struct dentry *dentry) {atomic_inc (&dentry->d_count); -if (!list_empty (&dentry->d_lru)) { +if (!list_empty (&dentry->d_unused)) { dentry_stat.nr_unused--; -list_del_init (&dentry->d_lru); +list_del_init (&dentry->d_unused); }return dentry; }
@@ -348,6 +366,7 @@ static inline void prune_one_dentry(stru {struct dentry * parent; +dentry_del_lru (dentry);__d_drop (dentry);list_del (&dentry->d_child); dentry_stat.nr_dentry--;/* For d_free, below */
@@ -385,6 +404,37 @@ static void prune_dcache(int count)list_del_init (tmp);prefetch (dentry_unused.prev); dentry_stat.nr_unused--; + dentry = list_entry(tmp, struct dentry, d_unused); + +spin_lock (&dentry->d_lock); +/* +* We found an inuse dentry which was not removed from +* dentry_unused because of laziness during lookup. Do not free +* it - just keep it off the dentry_unused list. +*/ +if (atomic_read (&dentry->d_count)) { +spin_unlock (&dentry->d_lock); +continue ; + } +if (dentry->d_flags &DCACHE_REFERENCED ) + dentry->d_flags &= ~DCACHE_REFERENCED ; +prune_one_dentry (dentry); + } +spin_unlock (&dcache_lock); +} + +static unsigned long prune_dcache_lru (struct list_head *list,unsigned long count) +{ +unsigned long pruned =0 ; +spin_lock (&dcache_lock); +for (; count ; count--) { +struct dentry *dentry; +struct list_head *tmp; + + tmp = list->prev; +if (tmp == list) +break ; +prefetch (tmp->prev); dentry = list_entry(tmp, struct dentry, d_lru);spin_lock (&dentry->d_lock);
@@ -394,22 +444,32 @@ static void prune_dcache(int count)* it - just keep it off the dentry_unused list. */ if (atomic_read (&dentry->d_count)) { +if (!list_empty (&dentry->d_unused)) { +list_del_init (&dentry->d_unused); + dentry_stat.nr_unused--; + }spin_unlock (&dentry->d_lock);continue ; }/* If the dentry was recently referenced, don't free it. */ if (dentry->d_flags &DCACHE_REFERENCED ) { dentry->d_flags &= ~DCACHE_REFERENCED ; -list_add (&dentry->d_lru, &dentry_unused); - dentry_stat.nr_unused++; +list_del (&dentry->d_lru); +list_add (&dentry->d_lru, list);spin_unlock (&dentry->d_lock);continue ; } +list_del_init (&dentry->d_unused); + dentry_stat.nr_unused--;prune_one_dentry (dentry); + pruned++; }spin_unlock (&dcache_lock); + +return pruned; } +/* * Shrink the dcache for the specified super block. * This allows us to unmount a device without disturbing
@@ -446,7 +506,7 @@ void shrink_dcache_sb(struct super_blockwhile (next != &dentry_unused) { tmp = next; next = tmp->next; - dentry = list_entry(tmp, struct dentry, d_lru); + dentry = list_entry(tmp, struct dentry, d_unused);if (dentry->d_sb != sb)continue ;list_del (tmp);
@@ -461,7 +521,7 @@ repeat:while (next != &dentry_unused) { tmp = next; next = tmp->next; - dentry = list_entry(tmp, struct dentry, d_lru); + dentry = list_entry(tmp, struct dentry, d_unused);if (dentry->d_sb != sb)continue ; dentry_stat.nr_unused--;
@@ -551,16 +611,16 @@ resume: struct dentry *dentry = list_entry(tmp, struct dentry, d_child); next = tmp->next; -if (!list_empty (&dentry->d_lru)) { +if (!list_empty (&dentry->d_unused)) { dentry_stat.nr_unused--; -list_del_init (&dentry->d_lru); +list_del_init (&dentry->d_unused); }/* * move only zero ref count dentries to the end * of the unused list for prune_dcache */ if (!atomic_read (&dentry->d_count)) { -list_add (&dentry->d_lru, dentry_unused.prev); +list_add (&dentry->d_unused, dentry_unused.prev); dentry_stat.nr_unused++; found++; }
@@ -626,9 +686,9 @@ void shrink_dcache_anon(struct hlist_heaspin_lock (&dcache_lock);hlist_for_each (lp, head) { struct dentry *this = hlist_entry(lp, struct dentry, d_hash); -if (!list_empty (&this->d_lru)) { +if (!list_empty (&this->d_unused)) { dentry_stat.nr_unused--; -list_del (&this->d_lru); +list_del (&this->d_unused); }/*
@@ -636,7 +696,7 @@ void shrink_dcache_anon(struct hlist_hea* of the unused list for prune_dcache */ if (!atomic_read (&this->d_count)) { -list_add_tail (&this->d_lru, &dentry_unused); +list_add_tail (&this->d_unused, &dentry_unused); dentry_stat.nr_unused++; found++; }
@@ -658,14 +718,16 @@ void shrink_dcache_anon(struct hlist_hea* * In this case we return -1 to tell the caller that we baled. */ -static int shrink_dcache_memory (int nr,unsigned int gfp_mask) +static unsigned long shrink_dcache_memory (struct zone_shrinker *zs, +unsigned long nr, +unsigned int gfp_mask) {if (nr) {if (!(gfp_mask & __GFP_FS))return -1 ; -prune_dcache (nr); + zs->nr -=prune_dcache_lru (&zs->lru, nr); } -return (dentry_stat.nr_unused /100 ) * sysctl_vfs_cache_pressure; +return (zs->nr /100 ) * sysctl_vfs_cache_pressure; }/**
@@ -695,7 +757,7 @@ struct dentry *d_alloc(struct dentry * p } }else { dname = dentry->d_iname; - } + } dentry->d_name.name = dname; dentry->d_name.len = name->len;
@@ -716,6 +778,7 @@ struct dentry *d_alloc(struct dentry * p dentry->d_bucket =NULL ;INIT_HLIST_NODE (&dentry->d_hash);INIT_LIST_HEAD (&dentry->d_lru); +INIT_LIST_HEAD (&dentry->d_unused);INIT_LIST_HEAD (&dentry->d_subdirs);INIT_LIST_HEAD (&dentry->d_alias);
@@ -727,6 +790,7 @@ struct dentry *d_alloc(struct dentry * p }spin_lock (&dcache_lock); +dentry_add_lru (dentry);if (parent)list_add (&dentry->d_child, &parent->d_subdirs); dentry_stat.nr_dentry++;
@@ -831,7 +895,7 @@ struct dentry * d_alloc_anon(struct inodreturn NULL ; tmp->d_parent = tmp;/* make sure dput doesn't croak */ - +spin_lock (&dcache_lock);if (S_ISDIR (inode->i_mode) && !list_empty (&inode->i_dentry)) {/* A directory can only have one dentry.
@@ -969,7 +1033,7 @@ struct dentry * __d_lookup(struct dentrystruct hlist_node *node;rcu_read_lock (); - + hlist_for_each (node, head) {struct dentry *dentry;struct qstr *qstr;
@@ -1592,8 +1656,10 @@ static void __init dcache_init(unsigned0 ,SLAB_RECLAIM_ACCOUNT |SLAB_PANIC , NULL, NULL); - -set_shrinker (DEFAULT_SEEKS , shrink_dcache_memory); + + zone_shrinker =set_zone_shrinker (shrink_dcache_memory,DEFAULT_SEEKS ); +if (zone_shrinker <0 ) +BUG (); }/* SLAB cache for __getname() consumers */
include/linux/mmzone.h renamed from include/linux/mmzone.h~perzone-slab +3 -1
@@ -142,7 +142,7 @@ struct zone {ZONE_PADDING (_pad1_) - spinlock_t lru_lock; + spinlock_t lru_lock;struct list_head active_list;struct list_head inactive_list;unsigned long nr_scan_active;
@@ -152,6 +152,8 @@ struct zone {int all_unreclaimable;/* All pages pinned */ unsigned long pages_scanned;/* since last reclaim */ +struct list_head zone_shrinker_list; +ZONE_PADDING (_pad2_)/*
mm/page_alloc.c renamed from mm/page_alloc.c~perzone-slab +1 -0
@@ -1495,6 +1495,7 @@ static void __init free_area_init_core(s zone->nr_scan_inactive =0 ; zone->nr_active =0 ; zone->nr_inactive =0 ; +INIT_LIST_HEAD (&zone->zone_shrinker_list);if (!size)continue ;
include/linux/mm.h renamed from include/linux/mm.h~perzone-slab +19 -0
@@ -575,6 +575,25 @@ struct shrinker;extern struct shrinker *set_shrinker (int ,shrinker_t );extern void remove_shrinker (struct shrinker *shrinker); +struct zone_shrinker ; +typedef unsigned long (*zone_shrinker_fn )(struct zone_shrinker *zs, +unsigned long nr_to_scan, +unsigned int gfp_mask); +struct zone_shrinker { +struct list_head lru; +unsigned long nr; +zone_shrinker_fn shrinker; +unsigned long nr_scan; +int seeks; + +int idx; +struct list_head list; +}; + +int set_zone_shrinker (zone_shrinker_fn ,int ); +struct zone_shrinker *get_zone_shrinker (struct zone *,int ); +void remove_zone_shrinker (int ); +/* * On a two-level page table, this ends up being trivial. Thus the * inlining and the symmetry break with pte_alloc_map() that does all
mm/vmscan.c renamed from mm/vmscan.c~perzone-slab +110 -29
213 lines not on this page · raw message
include/linux/dcache.h renamed from include/linux/dcache.h~perzone-slab +1 -0
@@ -95,6 +95,7 @@ struct dentry {struct qstr d_name;struct list_head d_lru;/* LRU list */ +struct list_head d_unused;/* unused list */ struct list_head d_child;/* child of parent list */ struct list_head d_subdirs;/* our children */ struct list_head d_alias;/* inode alias list */
_
Quoted 13 lines
Oops, forgot to CC linux-mm.
Quoted 10 lines · Nick Piggin
This patch is only intended for comments.
This implements (crappy?) infrastructure for per-zone slab LRUs for reclaimable slabs, and moves
dcache.cover to use that.The global unused list is retained to reduce intrusiveness, and another per-zone LRU list is added (which are still protected with the global dcache
lock). This is an attempt to make slab scanning more robust on highmem and NUMA systems.
Do we have slab that goes in highmem anywhere? I thought not .... 64 bit NUMA makes a lot of sense though.
M
--
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>
Quoted 9 lines · Martin J. Bligh
Quoted 9 lines
Oops, forgot to CC linux-mm.
Quoted 5 lines · Nick Piggin
This patch is only intended for comments.
This implements (crappy?) infrastructure for per-zone slab LRUs for reclaimable slabs, and moves
dcache.cover to use that.
Quoted 10 lines
Quoted 5 lines
Quoted 5 lines
The global unused list is retained to reduce intrusiveness, and another per-zone LRU list is added (which are still protected with the global dcache lock). This is an attempt to make slab scanning more robust on highmem and NUMA systems.
Do we have slab that goes in highmem anywhere? I thought not .... 64 bit NUMA makes a lot of sense though.
I don't think so, but it still (I think) allows a general slab pressure forumula for highmem and muliple ZONE_NORMAL NUMA that doesn't blow up like our current one can.
The per-zone lists I'd say would have to help somewhat on the performance side of things as far as not taking remote cache misses during scanning. It should also mean that a low memory node will not globally shrink masses of slab from nodes that have plenty of memory.
As far as the dependant inodes problem goes - it could be a significant problem, and I think would need to be solved before perzone slab LRUs are a viable option. The most simplistic way I can see to solve it is: if we scan an inode that is pinned by dentries, scan its pinning dentries instead.
--
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>