Skip to contentSkip to conversation

Scalable page cache - take two

PATCH In review Momchil Velikov 5 participants 5 messages Dec 23, 2001 – Dec 27, 2001

Subsystems (9)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Scalable page cache - take two

Hi there,

This is the second mutation of the scalable page cache patch. It:

- removes the global page cache hash table and the associated lock

- implements the page cache as a per mapping radix tree. This increases the size of ``struct inode'' by 8 bytes (in 32bit ports). The branch factor is a compile time constant ``RAD_MAP_SHIFT'' (currently 7, 128 pointers)

- decreases the size of ``struct page'' by removing ``next_hash'' and ``pprev_hash' fields as they are no longer needed for the page cache. NOTE that this curently breaks sparc64 and arm ports. The sparc64 port is trivialy fixable.

The patch is stable on UP (survives make -j6) and does not have noticeable performance impact (at least for the kernel compile benchmark) in either direction.

Regards,
-velco

drivers/block/rd.c +1 -3
diff -Nru a/drivers/block/rd.c b/drivers/block/rd.c
@@ -243,7 +243,6 @@
 
 	do {
 		int count;
-		struct page ** hash;
 		struct page * page;
 		char * src, * dst;
 		int unlock = 0;
@@ -253,8 +252,7 @@
 			count = size;
 		size -= count;
 
-		hash = page_hash(mapping, index);
-		page = __find_get_page(mapping, index, hash);
+		page = __find_get_page(mapping, index);
 		if (!page) {
 			page = grab_cache_page(mapping, index);
 			err = -ENOMEM;
fs/inode.c +2 -0
diff -Nru a/fs/inode.c b/fs/inode.c
@@ -110,6 +110,8 @@
 		sema_init(&inode->i_sem, 1);
 		sema_init(&inode->i_zombie, 1);
 		spin_lock_init(&inode->i_data.i_shared_lock);
+		inode->i_data.root = NULL;
+		inode->i_data.height = 0;
 	}
 }
 
include/linux/fs.h +2 -0
diff -Nru a/include/linux/fs.h b/include/linux/fs.h
@@ -403,6 +403,8 @@
 	struct vm_area_struct	*i_mmap;	/* list of private mappings */
 	struct vm_area_struct	*i_mmap_shared; /* list of shared mappings */
 	spinlock_t		i_shared_lock;  /* and spinlock protecting it */
+	struct rat_node		*root;
+	unsigned int		height;
 	int			gfp_mask;	/* how to allocate the pages */
 };
 
include/linux/mm.h +20 -6
diff -Nru a/include/linux/mm.h b/include/linux/mm.h
@@ -152,15 +152,12 @@
 	struct list_head list;		/* ->mapping has some page lists. */
 	struct address_space *mapping;	/* The inode (or ...) we belong to. */
 	unsigned long index;		/* Our offset within mapping. */
-	struct page *next_hash;		/* Next page sharing our hash bucket in
-					   the pagecache hash table. */
 	atomic_t count;			/* Usage count, see below. */
 	unsigned long flags;		/* atomic flags, some possibly
 					   updated asynchronously */
 	struct list_head lru;		/* Pageout list, eg. active_list;
 					   protected by pagemap_lru_lock !! */
 	wait_queue_head_t wait;		/* Page locked?  Stand in line... */
-	struct page **pprev_hash;	/* Complement to *next_hash. */
 	struct buffer_head * buffers;	/* Buffer maps us to a disk block. */
 	void *virtual;			/* Kernel virtual address (NULL if
 					   not kmapped, ie. highmem) */
@@ -228,9 +225,8 @@
  * using the page->list list_head. These fields are also used for
  * freelist managemet (when page->count==0).
  *
- * There is also a hash table mapping (mapping,index) to the page
- * in memory if present. The lists for this hash table use the fields
- * page->next_hash and page->pprev_hash.
+ * There is also a per-mapping radix tree mapping index to the page
+ * in memory if present. The tree is rooted at mapping->root.  
  *
  * All process pages can do I/O:
  * - inode pages may need to be read from disk,
@@ -507,6 +503,24 @@
 		return 1;
 	else
 		return 0;
+}
+
+static inline void add_page_to_inode_queue(struct address_space *mapping, struct page * page)
+{
+	struct list_head *head = &mapping->clean_pages;
+
+	mapping->nrpages++;
+	list_add(&page->list, head);
+	page->mapping = mapping;
+}
+
+static inline void remove_page_from_inode_queue(struct page * page)
+{
+	struct address_space * mapping = page->mapping;
+
+	mapping->nrpages--;
+	list_del(&page->list);
+	page->mapping = NULL;
 }
 
 struct zone_t;
include/linux/pagemap.h +9 -34

62 lines not on this page · raw message

include/linux/rat.h new file +81 -0

81 lines not on this page · raw message

include/linux/swap.h +3 -1
diff -Nru a/include/linux/swap.h b/include/linux/swap.h
@@ -97,7 +97,7 @@
 struct task_struct;
 struct vm_area_struct;
 struct sysinfo;
-
+struct address_space;
 struct zone_t;
 
 /* linux/mm/swap.c */
@@ -127,6 +127,8 @@
 extern int add_to_swap_cache(struct page *, swp_entry_t);
 extern void __delete_from_swap_cache(struct page *page);
 extern void delete_from_swap_cache(struct page *page);
+extern int move_to_swap_cache (struct page *page, swp_entry_t entry);
+extern int move_from_swap_cache (struct page *page, unsigned long index, struct address_space *mapping);
 extern void free_page_and_swap_cache(struct page *page);
 extern struct page * lookup_swap_cache(swp_entry_t);
 extern struct page * read_swap_cache_async(swp_entry_t);
init/main.c +1 -1
diff -Nru a/init/main.c b/init/main.c
@@ -599,7 +599,7 @@
 	proc_caches_init();
 	vfs_caches_init(mempages);
 	buffer_init(mempages);
-	page_cache_init(mempages);
+	page_cache_init ();
 #if defined(CONFIG_ARCH_S390)
 	ccwcache_init();
 #endif
kernel/ksyms.c +0 -2
diff -Nru a/kernel/ksyms.c b/kernel/ksyms.c
@@ -216,8 +216,6 @@
 EXPORT_SYMBOL(generic_file_mmap);
 EXPORT_SYMBOL(generic_ro_fops);
 EXPORT_SYMBOL(generic_buffer_fdatasync);
-EXPORT_SYMBOL(page_hash_bits);
-EXPORT_SYMBOL(page_hash_table);
 EXPORT_SYMBOL(file_lock_list);
 EXPORT_SYMBOL(locks_init_lock);
 EXPORT_SYMBOL(locks_copy_lock);
mm/Makefile +1 -1
diff -Nru a/mm/Makefile b/mm/Makefile
@@ -14,7 +14,7 @@
 obj-y	 := memory.o mmap.o filemap.o mprotect.o mlock.o mremap.o \
 	    vmalloc.o slab.o bootmem.o swap.o vmscan.o page_io.o \
 	    page_alloc.o swap_state.o swapfile.o numa.o oom_kill.o \
-	    shmem.o
+	    shmem.o rat.o
 
 obj-$(CONFIG_HIGHMEM) += highmem.o
 
mm/filemap.c +190 -233

874 lines not on this page · raw message

mm/rat.c new file +187 -0

187 lines not on this page · raw message

mm/shmem.c +45 -32

135 lines not on this page · raw message

mm/swap_state.c +94 -5

129 lines not on this page · raw message

mm/swapfile.c +4 -4
diff -Nru a/mm/swapfile.c b/mm/swapfile.c
@@ -239,10 +239,10 @@
 		/* Is the only swap cache user the cache itself? */
 		if (p->swap_map[SWP_OFFSET(entry)] == 1) {
 			/* Recheck the page count with the pagecache lock held.. */
-			spin_lock(&pagecache_lock);
+			spin_lock(&swapper_space.i_shared_lock);
 			if (page_count(page) - !!page->buffers == 2)
 				retval = 1;
-			spin_unlock(&pagecache_lock);
+			spin_unlock(&swapper_space.i_shared_lock);
 		}
 		swap_info_put(p);
 	}
@@ -307,13 +307,13 @@
 	retval = 0;
 	if (p->swap_map[SWP_OFFSET(entry)] == 1) {
 		/* Recheck the page count with the pagecache lock held.. */
-		spin_lock(&pagecache_lock);
+		spin_lock(&swapper_space.i_shared_lock);
 		if (page_count(page) - !!page->buffers == 2) {
 			__delete_from_swap_cache(page);
 			SetPageDirty(page);
 			retval = 1;
 		}
-		spin_unlock(&pagecache_lock);
+		spin_unlock(&swapper_space.i_shared_lock);
 	}
 	swap_info_put(p);
 
mm/vmscan.c +28 -10
diff -Nru a/mm/vmscan.c b/mm/vmscan.c
@@ -136,10 +136,16 @@
 		 * (adding to the page cache will clear the dirty
 		 * and uptodate bits, so we need to do it again)
 		 */
-		if (add_to_swap_cache(page, entry) == 0) {
+		switch (add_to_swap_cache (page, entry)) {
+		case 0:
 			SetPageUptodate(page);
 			set_page_dirty(page);
 			goto set_swap_pte;
+		case -ENOMEM:
+			swap_free (entry);
+			goto preserve;
+		default:
+			break;
 		}
 		/* Raced with "speculative" read_swap_cache_async */
 		swap_free(entry);
@@ -337,6 +343,7 @@
 static int shrink_cache(int nr_pages, zone_t * classzone, unsigned int gfp_mask, int priority)
 {
 	struct list_head * entry;
+	struct address_space *mapping;
 	int max_scan = nr_inactive_pages / priority;
 	int max_mapped = min((nr_pages << (10 - priority)), max_scan / 10);
 
@@ -391,7 +398,9 @@
 			continue;
 		}
 
-		if (PageDirty(page) && is_page_cache_freeable(page) && page->mapping) {
+		mapping = page->mapping;
+
+		if (PageDirty(page) && is_page_cache_freeable(page) && mapping) {
 			/*
 			 * It is not critical here to write it only if
 			 * the page is unmapped beause any direct writer
@@ -402,7 +411,7 @@
 			 */
 			int (*writepage)(struct page *);
 
-			writepage = page->mapping->a_ops->writepage;
+			writepage = mapping->a_ops->writepage;
 			if ((gfp_mask & __GFP_FS) && writepage) {
 				ClearPageDirty(page);
 				SetPageLaunder(page);
@@ -429,7 +438,7 @@
 			page_cache_get(page);
 
 			if (try_to_release_page(page, gfp_mask)) {
-				if (!page->mapping) {
+				if (!mapping) {
 					/*
 					 * We must not allow an anon page
 					 * with no buffers to be visible on
@@ -466,13 +475,22 @@
 			}
 		}
 
-		spin_lock(&pagecache_lock);
+		/*
+		 * Page is locked, so mapping can't change under our
+		 * feet.
+		 */
+		if (!mapping) {
+			UnlockPage (page);
+			goto page_mapped;
+		}
+
+		spin_lock(&mapping->i_shared_lock);
 
 		/*
 		 * this is the non-racy check for busy page.
 		 */
-		if (!page->mapping || !is_page_cache_freeable(page)) {
-			spin_unlock(&pagecache_lock);
+		if (!is_page_cache_freeable(page)) {
+			spin_unlock(&mapping->i_shared_lock);
 			UnlockPage(page);
 page_mapped:
 			if (--max_mapped >= 0)
@@ -492,7 +510,7 @@
 		 * the page is freeable* so not in use by anybody.
 		 */
 		if (PageDirty(page)) {
-			spin_unlock(&pagecache_lock);
+			spin_unlock(&mapping->i_shared_lock);
 			UnlockPage(page);
 			continue;
 		}
@@ -500,12 +518,12 @@
 		/* point of no return */
 		if (likely(!PageSwapCache(page))) {
 			__remove_inode_page(page);
-			spin_unlock(&pagecache_lock);
+			spin_unlock(&mapping->i_shared_lock);
 		} else {
 			swp_entry_t swap;
 			swap.val = page->index;
 			__delete_from_swap_cache(page);
-			spin_unlock(&pagecache_lock);
+			spin_unlock(&mapping->i_shared_lock);
 			swap_free(swap);
 		}
 
#1 lore raw
Quoted 9 lines · Momchil Velikov

This is the second mutation of the scalable page cache patch. It:

- removes the global page cache hash table and the associated lock

- implements the page cache as a per mapping radix tree. This increases the size of ``struct inode'' by 8 bytes (in 32bit ports). The branch factor is a compile time constant ``RAD_MAP_SHIFT'' (currently 7, 128 pointers)

Quoted 8 lines

- decreases the size of ``struct page'' by removing ``next_hash'' and ``pprev_hash' fields as they are no longer needed for the page cache. NOTE that this curently breaks sparc64 and arm ports. The sparc64 port is trivialy fixable.

The patch is stable on UP (survives make -j6) and does not have noticeable performance impact (at least for the kernel compile benchmark) in either direction.

Beautiful programming!

Cheers,
Bill

#2 lore raw
In reply to Momchil Velikov

Hi,

Quoted 1 line

This is the second mutation of the scalable page cache patch. It:

I ran the dbench test over it just to annoy Andrew:

http://samba.org/~anton/linux/pagecache_locking/2/

This is the still 12 way ppc64, the results are similar to the last test. It confirms that the current pagecache locking is a bottleneck for larger SMP machines.

We quickly hit the following spinlocks with the pagecache lock out of the way:

ext2_get_block: BKL
refile_buffer: lru_list_lock
remove_from_queues: lru_list_lock
try_to_free_buffers: lru_list_lock
d_lookup: dcache_lock
ext2_discard_prealloc: BKL

Is someone working on removing the BKL from ext2 in 2.5?

Anton

#3 lore raw
In reply to Momchil Velikov
Quoted 1 line · Momchil Velikov

This is the second mutation of the scalable page cache patch.

[....]

Quoted 3 lines

The patch is stable on UP (survives make -j6) and does not have noticeable performance impact (at least for the kernel compile benchmark) in either direction.

Works perfectly! I'll do some further heavy testing, up to now it runs smooth and fine.

Happy X-Mas! ;)

Signature · 1 line
# Heinz Diehl, 68259 Mannheim, Germany
#4 lore raw
In reply to Momchil Velikov
Quoted 7 lines · Anton Blanchard

This is the still 12 way ppc64, the results are similar to the last test. It confirms that the current pagecache locking is a bottleneck for larger SMP machines.

We quickly hit the following spinlocks with the pagecache lock out of the way:

Quoted 8 lines

ext2_get_block: BKL
refile_buffer: lru_list_lock
remove_from_queues: lru_list_lock
try_to_free_buffers: lru_list_lock
d_lookup: dcache_lock
ext2_discard_prealloc: BKL

Is someone working on removing the BKL from ext2 in 2.5?

Al has already done most of the work, it won't take much to complete it. It wasn't a priority before 2.5 (stability was).

It's nice to see somebody still cares about the speed of Ext2 ;)

--
Daniel