Commit eb2be189 authored by Nick Piggin's avatar Nick Piggin Committed by Linus Torvalds

mm: buffered write cleanup

Quite a bit of code is used in maintaining these "cached pages" that are
probably pretty unlikely to get used. It would require a narrow race where
the page is inserted concurrently while this process is allocating a page
in order to create the spare page. Then a multi-page write into an uncached
part of the file, to make use of it.

Next, the buffered write path (and others) uses its own LRU pagevec when it
should be just using the per-CPU LRU pagevec (which will cut down on both data
and code size cacheline footprint). Also, these private LRU pagevecs are
emptied after just a very short time, in contrast with the per-CPU pagevecs
that are persistent. Net result: 7.3 times fewer lru_lock acquisitions required
to add the pages to pagecache for a bulk write (in 4K chunks).

[this gets rid of some cond_resched() calls in readahead.c and mpage.c due
 to clashes in -mm. What put them there, and why? ]
Signed-off-by: default avatarNick Piggin <npiggin@suse.de>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 64649a58
...@@ -379,31 +379,25 @@ mpage_readpages(struct address_space *mapping, struct list_head *pages, ...@@ -379,31 +379,25 @@ mpage_readpages(struct address_space *mapping, struct list_head *pages,
struct bio *bio = NULL; struct bio *bio = NULL;
unsigned page_idx; unsigned page_idx;
sector_t last_block_in_bio = 0; sector_t last_block_in_bio = 0;
struct pagevec lru_pvec;
struct buffer_head map_bh; struct buffer_head map_bh;
unsigned long first_logical_block = 0; unsigned long first_logical_block = 0;
clear_buffer_mapped(&map_bh); clear_buffer_mapped(&map_bh);
pagevec_init(&lru_pvec, 0);
for (page_idx = 0; page_idx < nr_pages; page_idx++) { for (page_idx = 0; page_idx < nr_pages; page_idx++) {
struct page *page = list_entry(pages->prev, struct page, lru); struct page *page = list_entry(pages->prev, struct page, lru);
prefetchw(&page->flags); prefetchw(&page->flags);
list_del(&page->lru); list_del(&page->lru);
if (!add_to_page_cache(page, mapping, if (!add_to_page_cache_lru(page, mapping,
page->index, GFP_KERNEL)) { page->index, GFP_KERNEL)) {
bio = do_mpage_readpage(bio, page, bio = do_mpage_readpage(bio, page,
nr_pages - page_idx, nr_pages - page_idx,
&last_block_in_bio, &map_bh, &last_block_in_bio, &map_bh,
&first_logical_block, &first_logical_block,
get_block); get_block);
if (!pagevec_add(&lru_pvec, page))
__pagevec_lru_add(&lru_pvec);
} else {
page_cache_release(page);
} }
page_cache_release(page);
} }
pagevec_lru_add(&lru_pvec);
BUG_ON(!list_empty(pages)); BUG_ON(!list_empty(pages));
if (bio) if (bio)
mpage_bio_submit(READ, bio); mpage_bio_submit(READ, bio);
......
...@@ -666,27 +666,22 @@ EXPORT_SYMBOL(find_lock_page); ...@@ -666,27 +666,22 @@ EXPORT_SYMBOL(find_lock_page);
struct page *find_or_create_page(struct address_space *mapping, struct page *find_or_create_page(struct address_space *mapping,
pgoff_t index, gfp_t gfp_mask) pgoff_t index, gfp_t gfp_mask)
{ {
struct page *page, *cached_page = NULL; struct page *page;
int err; int err;
repeat: repeat:
page = find_lock_page(mapping, index); page = find_lock_page(mapping, index);
if (!page) { if (!page) {
if (!cached_page) { page = __page_cache_alloc(gfp_mask);
cached_page = if (!page)
__page_cache_alloc(gfp_mask);
if (!cached_page)
return NULL; return NULL;
} err = add_to_page_cache_lru(page, mapping, index, gfp_mask);
err = add_to_page_cache_lru(cached_page, mapping, if (unlikely(err)) {
index, gfp_mask); page_cache_release(page);
if (!err) { page = NULL;
page = cached_page; if (err == -EEXIST)
cached_page = NULL;
} else if (err == -EEXIST)
goto repeat; goto repeat;
} }
if (cached_page) }
page_cache_release(cached_page);
return page; return page;
} }
EXPORT_SYMBOL(find_or_create_page); EXPORT_SYMBOL(find_or_create_page);
...@@ -872,10 +867,8 @@ void do_generic_mapping_read(struct address_space *mapping, ...@@ -872,10 +867,8 @@ void do_generic_mapping_read(struct address_space *mapping,
pgoff_t prev_index; pgoff_t prev_index;
unsigned long offset; /* offset into pagecache page */ unsigned long offset; /* offset into pagecache page */
unsigned int prev_offset; unsigned int prev_offset;
struct page *cached_page;
int error; int error;
cached_page = NULL;
index = *ppos >> PAGE_CACHE_SHIFT; index = *ppos >> PAGE_CACHE_SHIFT;
prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT; prev_index = ra->prev_pos >> PAGE_CACHE_SHIFT;
prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1); prev_offset = ra->prev_pos & (PAGE_CACHE_SIZE-1);
...@@ -1031,23 +1024,20 @@ no_cached_page: ...@@ -1031,23 +1024,20 @@ no_cached_page:
* Ok, it wasn't cached, so we need to create a new * Ok, it wasn't cached, so we need to create a new
* page.. * page..
*/ */
if (!cached_page) { page = page_cache_alloc_cold(mapping);
cached_page = page_cache_alloc_cold(mapping); if (!page) {
if (!cached_page) {
desc->error = -ENOMEM; desc->error = -ENOMEM;
goto out; goto out;
} }
} error = add_to_page_cache_lru(page, mapping,
error = add_to_page_cache_lru(cached_page, mapping,
index, GFP_KERNEL); index, GFP_KERNEL);
if (error) { if (error) {
page_cache_release(page);
if (error == -EEXIST) if (error == -EEXIST)
goto find_page; goto find_page;
desc->error = error; desc->error = error;
goto out; goto out;
} }
page = cached_page;
cached_page = NULL;
goto readpage; goto readpage;
} }
...@@ -1057,8 +1047,6 @@ out: ...@@ -1057,8 +1047,6 @@ out:
ra->prev_pos |= prev_offset; ra->prev_pos |= prev_offset;
*ppos = ((loff_t)index << PAGE_CACHE_SHIFT) + offset; *ppos = ((loff_t)index << PAGE_CACHE_SHIFT) + offset;
if (cached_page)
page_cache_release(cached_page);
if (filp) if (filp)
file_accessed(filp); file_accessed(filp);
} }
...@@ -1502,35 +1490,28 @@ static struct page *__read_cache_page(struct address_space *mapping, ...@@ -1502,35 +1490,28 @@ static struct page *__read_cache_page(struct address_space *mapping,
int (*filler)(void *,struct page*), int (*filler)(void *,struct page*),
void *data) void *data)
{ {
struct page *page, *cached_page = NULL; struct page *page;
int err; int err;
repeat: repeat:
page = find_get_page(mapping, index); page = find_get_page(mapping, index);
if (!page) { if (!page) {
if (!cached_page) { page = page_cache_alloc_cold(mapping);
cached_page = page_cache_alloc_cold(mapping); if (!page)
if (!cached_page)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} err = add_to_page_cache_lru(page, mapping, index, GFP_KERNEL);
err = add_to_page_cache_lru(cached_page, mapping, if (unlikely(err)) {
index, GFP_KERNEL); page_cache_release(page);
if (err == -EEXIST) if (err == -EEXIST)
goto repeat; goto repeat;
if (err < 0) {
/* Presumably ENOMEM for radix tree node */ /* Presumably ENOMEM for radix tree node */
page_cache_release(cached_page);
return ERR_PTR(err); return ERR_PTR(err);
} }
page = cached_page;
cached_page = NULL;
err = filler(data, page); err = filler(data, page);
if (err < 0) { if (err < 0) {
page_cache_release(page); page_cache_release(page);
page = ERR_PTR(err); page = ERR_PTR(err);
} }
} }
if (cached_page)
page_cache_release(cached_page);
return page; return page;
} }
...@@ -1606,40 +1587,6 @@ struct page *read_cache_page(struct address_space *mapping, ...@@ -1606,40 +1587,6 @@ struct page *read_cache_page(struct address_space *mapping,
} }
EXPORT_SYMBOL(read_cache_page); EXPORT_SYMBOL(read_cache_page);
/*
* If the page was newly created, increment its refcount and add it to the
* caller's lru-buffering pagevec. This function is specifically for
* generic_file_write().
*/
static inline struct page *
__grab_cache_page(struct address_space *mapping, unsigned long index,
struct page **cached_page, struct pagevec *lru_pvec)
{
int err;
struct page *page;
repeat:
page = find_lock_page(mapping, index);
if (!page) {
if (!*cached_page) {
*cached_page = page_cache_alloc(mapping);
if (!*cached_page)
return NULL;
}
err = add_to_page_cache(*cached_page, mapping,
index, GFP_KERNEL);
if (err == -EEXIST)
goto repeat;
if (err == 0) {
page = *cached_page;
page_cache_get(page);
if (!pagevec_add(lru_pvec, page))
__pagevec_lru_add(lru_pvec);
*cached_page = NULL;
}
}
return page;
}
/* /*
* The logic we want is * The logic we want is
* *
...@@ -1832,6 +1779,33 @@ generic_file_direct_write(struct kiocb *iocb, const struct iovec *iov, ...@@ -1832,6 +1779,33 @@ generic_file_direct_write(struct kiocb *iocb, const struct iovec *iov,
} }
EXPORT_SYMBOL(generic_file_direct_write); EXPORT_SYMBOL(generic_file_direct_write);
/*
* Find or create a page at the given pagecache position. Return the locked
* page. This function is specifically for buffered writes.
*/
static struct page *__grab_cache_page(struct address_space *mapping,
pgoff_t index)
{
int status;
struct page *page;
repeat:
page = find_lock_page(mapping, index);
if (likely(page))
return page;
page = page_cache_alloc(mapping);
if (!page)
return NULL;
status = add_to_page_cache_lru(page, mapping, index, GFP_KERNEL);
if (unlikely(status)) {
page_cache_release(page);
if (status == -EEXIST)
goto repeat;
return NULL;
}
return page;
}
ssize_t ssize_t
generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov, generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos, loff_t *ppos, unsigned long nr_segs, loff_t pos, loff_t *ppos,
...@@ -1842,15 +1816,10 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov, ...@@ -1842,15 +1816,10 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
const struct address_space_operations *a_ops = mapping->a_ops; const struct address_space_operations *a_ops = mapping->a_ops;
struct inode *inode = mapping->host; struct inode *inode = mapping->host;
long status = 0; long status = 0;
struct page *page;
struct page *cached_page = NULL;
struct pagevec lru_pvec;
const struct iovec *cur_iov = iov; /* current iovec */ const struct iovec *cur_iov = iov; /* current iovec */
size_t iov_offset = 0; /* offset in the current iovec */ size_t iov_offset = 0; /* offset in the current iovec */
char __user *buf; char __user *buf;
pagevec_init(&lru_pvec, 0);
/* /*
* handle partial DIO write. Adjust cur_iov if needed. * handle partial DIO write. Adjust cur_iov if needed.
*/ */
...@@ -1862,6 +1831,7 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov, ...@@ -1862,6 +1831,7 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
} }
do { do {
struct page *page;
pgoff_t index; /* Pagecache index for current page */ pgoff_t index; /* Pagecache index for current page */
unsigned long offset; /* Offset into pagecache page */ unsigned long offset; /* Offset into pagecache page */
unsigned long maxlen; /* Bytes remaining in current iovec */ unsigned long maxlen; /* Bytes remaining in current iovec */
...@@ -1888,7 +1858,8 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov, ...@@ -1888,7 +1858,8 @@ generic_file_buffered_write(struct kiocb *iocb, const struct iovec *iov,
fault_in_pages_readable(buf, maxlen); fault_in_pages_readable(buf, maxlen);
#endif #endif
page = __grab_cache_page(mapping,index,&cached_page,&lru_pvec);
page = __grab_cache_page(mapping, index);
if (!page) { if (!page) {
status = -ENOMEM; status = -ENOMEM;
break; break;
...@@ -1956,9 +1927,6 @@ fs_write_aop_error: ...@@ -1956,9 +1927,6 @@ fs_write_aop_error:
} while (count); } while (count);
*ppos = pos; *ppos = pos;
if (cached_page)
page_cache_release(cached_page);
/* /*
* For now, when the user asks for O_SYNC, we'll actually give O_DSYNC * For now, when the user asks for O_SYNC, we'll actually give O_DSYNC
*/ */
...@@ -1978,7 +1946,6 @@ fs_write_aop_error: ...@@ -1978,7 +1946,6 @@ fs_write_aop_error:
if (unlikely(file->f_flags & O_DIRECT) && written) if (unlikely(file->f_flags & O_DIRECT) && written)
status = filemap_write_and_wait(mapping); status = filemap_write_and_wait(mapping);
pagevec_lru_add(&lru_pvec);
return written ? written : status; return written ? written : status;
} }
EXPORT_SYMBOL(generic_file_buffered_write); EXPORT_SYMBOL(generic_file_buffered_write);
......
...@@ -58,28 +58,25 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages, ...@@ -58,28 +58,25 @@ int read_cache_pages(struct address_space *mapping, struct list_head *pages,
int (*filler)(void *, struct page *), void *data) int (*filler)(void *, struct page *), void *data)
{ {
struct page *page; struct page *page;
struct pagevec lru_pvec;
int ret = 0; int ret = 0;
pagevec_init(&lru_pvec, 0);
while (!list_empty(pages)) { while (!list_empty(pages)) {
page = list_to_page(pages); page = list_to_page(pages);
list_del(&page->lru); list_del(&page->lru);
if (add_to_page_cache(page, mapping, page->index, GFP_KERNEL)) { if (add_to_page_cache_lru(page, mapping,
page->index, GFP_KERNEL)) {
page_cache_release(page); page_cache_release(page);
continue; continue;
} }
page_cache_release(page);
ret = filler(data, page); ret = filler(data, page);
if (!pagevec_add(&lru_pvec, page)) if (unlikely(ret)) {
__pagevec_lru_add(&lru_pvec);
if (ret) {
put_pages_list(pages); put_pages_list(pages);
break; break;
} }
task_io_account_read(PAGE_CACHE_SIZE); task_io_account_read(PAGE_CACHE_SIZE);
} }
pagevec_lru_add(&lru_pvec);
return ret; return ret;
} }
...@@ -89,7 +86,6 @@ static int read_pages(struct address_space *mapping, struct file *filp, ...@@ -89,7 +86,6 @@ static int read_pages(struct address_space *mapping, struct file *filp,
struct list_head *pages, unsigned nr_pages) struct list_head *pages, unsigned nr_pages)
{ {
unsigned page_idx; unsigned page_idx;
struct pagevec lru_pvec;
int ret; int ret;
if (mapping->a_ops->readpages) { if (mapping->a_ops->readpages) {
...@@ -99,19 +95,15 @@ static int read_pages(struct address_space *mapping, struct file *filp, ...@@ -99,19 +95,15 @@ static int read_pages(struct address_space *mapping, struct file *filp,
goto out; goto out;
} }
pagevec_init(&lru_pvec, 0);
for (page_idx = 0; page_idx < nr_pages; page_idx++) { for (page_idx = 0; page_idx < nr_pages; page_idx++) {
struct page *page = list_to_page(pages); struct page *page = list_to_page(pages);
list_del(&page->lru); list_del(&page->lru);
if (!add_to_page_cache(page, mapping, if (!add_to_page_cache_lru(page, mapping,
page->index, GFP_KERNEL)) { page->index, GFP_KERNEL)) {
mapping->a_ops->readpage(filp, page); mapping->a_ops->readpage(filp, page);
if (!pagevec_add(&lru_pvec, page)) }
__pagevec_lru_add(&lru_pvec);
} else
page_cache_release(page); page_cache_release(page);
} }
pagevec_lru_add(&lru_pvec);
ret = 0; ret = 0;
out: out:
return ret; return ret;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment