Commit 55138e0b authored by Theodore Ts'o's avatar Theodore Ts'o

ext4: Adjust ext4_da_writepages() to write out larger contiguous chunks

Work around problems in the writeback code to force out writebacks in
larger chunks than just 4mb, which is just too small.  This also works
around limitations in the ext4 block allocator, which can't allocate
more than 2048 blocks at a time.  So we need to defeat the round-robin
characteristics of the writeback code and try to write out as many
blocks in one inode before allowing the writeback code to move on to
another inode.  We add a a new per-filesystem tunable,
max_writeback_mb_bump, which caps this to a default of 128mb per
inode.
Signed-off-by: default avatar"Theodore Ts'o" <tytso@mit.edu>
parent 71780577
...@@ -942,6 +942,7 @@ struct ext4_sb_info { ...@@ -942,6 +942,7 @@ struct ext4_sb_info {
unsigned int s_mb_stats; unsigned int s_mb_stats;
unsigned int s_mb_order2_reqs; unsigned int s_mb_order2_reqs;
unsigned int s_mb_group_prealloc; unsigned int s_mb_group_prealloc;
unsigned int s_max_writeback_mb_bump;
/* where last allocation was done - for stream allocation */ /* where last allocation was done - for stream allocation */
unsigned long s_mb_last_group; unsigned long s_mb_last_group;
unsigned long s_mb_last_start; unsigned long s_mb_last_start;
......
...@@ -1144,6 +1144,64 @@ static int check_block_validity(struct inode *inode, const char *msg, ...@@ -1144,6 +1144,64 @@ static int check_block_validity(struct inode *inode, const char *msg,
return 0; return 0;
} }
/*
* Return the number of dirty pages in the given inode starting at
* page frame idx.
*/
static pgoff_t ext4_num_dirty_pages(struct inode *inode, pgoff_t idx,
unsigned int max_pages)
{
struct address_space *mapping = inode->i_mapping;
pgoff_t index;
struct pagevec pvec;
pgoff_t num = 0;
int i, nr_pages, done = 0;
if (max_pages == 0)
return 0;
pagevec_init(&pvec, 0);
while (!done) {
index = idx;
nr_pages = pagevec_lookup_tag(&pvec, mapping, &index,
PAGECACHE_TAG_DIRTY,
(pgoff_t)PAGEVEC_SIZE);
if (nr_pages == 0)
break;
for (i = 0; i < nr_pages; i++) {
struct page *page = pvec.pages[i];
struct buffer_head *bh, *head;
lock_page(page);
if (unlikely(page->mapping != mapping) ||
!PageDirty(page) ||
PageWriteback(page) ||
page->index != idx) {
done = 1;
unlock_page(page);
break;
}
head = page_buffers(page);
bh = head;
do {
if (!buffer_delay(bh) &&
!buffer_unwritten(bh)) {
done = 1;
break;
}
} while ((bh = bh->b_this_page) != head);
unlock_page(page);
if (done)
break;
idx++;
num++;
if (num >= max_pages)
break;
}
pagevec_release(&pvec);
}
return num;
}
/* /*
* The ext4_get_blocks() function tries to look up the requested blocks, * The ext4_get_blocks() function tries to look up the requested blocks,
* and returns if the blocks are already mapped. * and returns if the blocks are already mapped.
...@@ -2743,8 +2801,10 @@ static int ext4_da_writepages(struct address_space *mapping, ...@@ -2743,8 +2801,10 @@ static int ext4_da_writepages(struct address_space *mapping,
int no_nrwrite_index_update; int no_nrwrite_index_update;
int pages_written = 0; int pages_written = 0;
long pages_skipped; long pages_skipped;
unsigned int max_pages;
int range_cyclic, cycled = 1, io_done = 0; int range_cyclic, cycled = 1, io_done = 0;
int needed_blocks, ret = 0, nr_to_writebump = 0; int needed_blocks, ret = 0;
long desired_nr_to_write, nr_to_writebump = 0;
loff_t range_start = wbc->range_start; loff_t range_start = wbc->range_start;
struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb); struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb);
...@@ -2771,16 +2831,6 @@ static int ext4_da_writepages(struct address_space *mapping, ...@@ -2771,16 +2831,6 @@ static int ext4_da_writepages(struct address_space *mapping,
if (unlikely(sbi->s_mount_flags & EXT4_MF_FS_ABORTED)) if (unlikely(sbi->s_mount_flags & EXT4_MF_FS_ABORTED))
return -EROFS; return -EROFS;
/*
* Make sure nr_to_write is >= sbi->s_mb_stream_request
* This make sure small files blocks are allocated in
* single attempt. This ensure that small files
* get less fragmented.
*/
if (wbc->nr_to_write < sbi->s_mb_stream_request) {
nr_to_writebump = sbi->s_mb_stream_request - wbc->nr_to_write;
wbc->nr_to_write = sbi->s_mb_stream_request;
}
if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX) if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
range_whole = 1; range_whole = 1;
...@@ -2795,6 +2845,36 @@ static int ext4_da_writepages(struct address_space *mapping, ...@@ -2795,6 +2845,36 @@ static int ext4_da_writepages(struct address_space *mapping,
} else } else
index = wbc->range_start >> PAGE_CACHE_SHIFT; index = wbc->range_start >> PAGE_CACHE_SHIFT;
/*
* This works around two forms of stupidity. The first is in
* the writeback code, which caps the maximum number of pages
* written to be 1024 pages. This is wrong on multiple
* levels; different architectues have a different page size,
* which changes the maximum amount of data which gets
* written. Secondly, 4 megabytes is way too small. XFS
* forces this value to be 16 megabytes by multiplying
* nr_to_write parameter by four, and then relies on its
* allocator to allocate larger extents to make them
* contiguous. Unfortunately this brings us to the second
* stupidity, which is that ext4's mballoc code only allocates
* at most 2048 blocks. So we force contiguous writes up to
* the number of dirty blocks in the inode, or
* sbi->max_writeback_mb_bump whichever is smaller.
*/
max_pages = sbi->s_max_writeback_mb_bump << (20 - PAGE_CACHE_SHIFT);
if (!range_cyclic && range_whole)
desired_nr_to_write = wbc->nr_to_write * 8;
else
desired_nr_to_write = ext4_num_dirty_pages(inode, index,
max_pages);
if (desired_nr_to_write > max_pages)
desired_nr_to_write = max_pages;
if (wbc->nr_to_write < desired_nr_to_write) {
nr_to_writebump = desired_nr_to_write - wbc->nr_to_write;
wbc->nr_to_write = desired_nr_to_write;
}
mpd.wbc = wbc; mpd.wbc = wbc;
mpd.inode = mapping->host; mpd.inode = mapping->host;
...@@ -2914,6 +2994,7 @@ static int ext4_da_writepages(struct address_space *mapping, ...@@ -2914,6 +2994,7 @@ static int ext4_da_writepages(struct address_space *mapping,
out_writepages: out_writepages:
if (!no_nrwrite_index_update) if (!no_nrwrite_index_update)
wbc->no_nrwrite_index_update = 0; wbc->no_nrwrite_index_update = 0;
if (wbc->nr_to_write > nr_to_writebump)
wbc->nr_to_write -= nr_to_writebump; wbc->nr_to_write -= nr_to_writebump;
wbc->range_start = range_start; wbc->range_start = range_start;
trace_ext4_da_writepages_result(inode, wbc, ret, pages_written); trace_ext4_da_writepages_result(inode, wbc, ret, pages_written);
......
...@@ -2197,6 +2197,7 @@ EXT4_RW_ATTR_SBI_UI(mb_min_to_scan, s_mb_min_to_scan); ...@@ -2197,6 +2197,7 @@ EXT4_RW_ATTR_SBI_UI(mb_min_to_scan, s_mb_min_to_scan);
EXT4_RW_ATTR_SBI_UI(mb_order2_req, s_mb_order2_reqs); EXT4_RW_ATTR_SBI_UI(mb_order2_req, s_mb_order2_reqs);
EXT4_RW_ATTR_SBI_UI(mb_stream_req, s_mb_stream_request); EXT4_RW_ATTR_SBI_UI(mb_stream_req, s_mb_stream_request);
EXT4_RW_ATTR_SBI_UI(mb_group_prealloc, s_mb_group_prealloc); EXT4_RW_ATTR_SBI_UI(mb_group_prealloc, s_mb_group_prealloc);
EXT4_RW_ATTR_SBI_UI(max_writeback_mb_bump, s_max_writeback_mb_bump);
static struct attribute *ext4_attrs[] = { static struct attribute *ext4_attrs[] = {
ATTR_LIST(delayed_allocation_blocks), ATTR_LIST(delayed_allocation_blocks),
...@@ -2210,6 +2211,7 @@ static struct attribute *ext4_attrs[] = { ...@@ -2210,6 +2211,7 @@ static struct attribute *ext4_attrs[] = {
ATTR_LIST(mb_order2_req), ATTR_LIST(mb_order2_req),
ATTR_LIST(mb_stream_req), ATTR_LIST(mb_stream_req),
ATTR_LIST(mb_group_prealloc), ATTR_LIST(mb_group_prealloc),
ATTR_LIST(max_writeback_mb_bump),
NULL, NULL,
}; };
...@@ -2679,6 +2681,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent) ...@@ -2679,6 +2681,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
} }
sbi->s_stripe = ext4_get_stripe_size(sbi); sbi->s_stripe = ext4_get_stripe_size(sbi);
sbi->s_max_writeback_mb_bump = 128;
/* /*
* set up enough so that it can read an inode * set up enough so that it can read an inode
......
...@@ -236,6 +236,7 @@ TRACE_EVENT(ext4_da_writepages, ...@@ -236,6 +236,7 @@ TRACE_EVENT(ext4_da_writepages,
__field( char, for_kupdate ) __field( char, for_kupdate )
__field( char, for_reclaim ) __field( char, for_reclaim )
__field( char, range_cyclic ) __field( char, range_cyclic )
__field( pgoff_t, writeback_index )
), ),
TP_fast_assign( TP_fast_assign(
...@@ -249,15 +250,17 @@ TRACE_EVENT(ext4_da_writepages, ...@@ -249,15 +250,17 @@ TRACE_EVENT(ext4_da_writepages,
__entry->for_kupdate = wbc->for_kupdate; __entry->for_kupdate = wbc->for_kupdate;
__entry->for_reclaim = wbc->for_reclaim; __entry->for_reclaim = wbc->for_reclaim;
__entry->range_cyclic = wbc->range_cyclic; __entry->range_cyclic = wbc->range_cyclic;
__entry->writeback_index = inode->i_mapping->writeback_index;
), ),
TP_printk("dev %s ino %lu nr_to_write %ld pages_skipped %ld range_start %llu range_end %llu nonblocking %d for_kupdate %d for_reclaim %d range_cyclic %d", TP_printk("dev %s ino %lu nr_to_write %ld pages_skipped %ld range_start %llu range_end %llu nonblocking %d for_kupdate %d for_reclaim %d range_cyclic %d writeback_index %lu",
jbd2_dev_to_name(__entry->dev), jbd2_dev_to_name(__entry->dev),
(unsigned long) __entry->ino, __entry->nr_to_write, (unsigned long) __entry->ino, __entry->nr_to_write,
__entry->pages_skipped, __entry->range_start, __entry->pages_skipped, __entry->range_start,
__entry->range_end, __entry->nonblocking, __entry->range_end, __entry->nonblocking,
__entry->for_kupdate, __entry->for_reclaim, __entry->for_kupdate, __entry->for_reclaim,
__entry->range_cyclic) __entry->range_cyclic,
(unsigned long) __entry->writeback_index)
); );
TRACE_EVENT(ext4_da_write_pages, TRACE_EVENT(ext4_da_write_pages,
...@@ -309,6 +312,7 @@ TRACE_EVENT(ext4_da_writepages_result, ...@@ -309,6 +312,7 @@ TRACE_EVENT(ext4_da_writepages_result,
__field( char, encountered_congestion ) __field( char, encountered_congestion )
__field( char, more_io ) __field( char, more_io )
__field( char, no_nrwrite_index_update ) __field( char, no_nrwrite_index_update )
__field( pgoff_t, writeback_index )
), ),
TP_fast_assign( TP_fast_assign(
...@@ -320,14 +324,16 @@ TRACE_EVENT(ext4_da_writepages_result, ...@@ -320,14 +324,16 @@ TRACE_EVENT(ext4_da_writepages_result,
__entry->encountered_congestion = wbc->encountered_congestion; __entry->encountered_congestion = wbc->encountered_congestion;
__entry->more_io = wbc->more_io; __entry->more_io = wbc->more_io;
__entry->no_nrwrite_index_update = wbc->no_nrwrite_index_update; __entry->no_nrwrite_index_update = wbc->no_nrwrite_index_update;
__entry->writeback_index = inode->i_mapping->writeback_index;
), ),
TP_printk("dev %s ino %lu ret %d pages_written %d pages_skipped %ld congestion %d more_io %d no_nrwrite_index_update %d", TP_printk("dev %s ino %lu ret %d pages_written %d pages_skipped %ld congestion %d more_io %d no_nrwrite_index_update %d writeback_index %lu",
jbd2_dev_to_name(__entry->dev), jbd2_dev_to_name(__entry->dev),
(unsigned long) __entry->ino, __entry->ret, (unsigned long) __entry->ino, __entry->ret,
__entry->pages_written, __entry->pages_skipped, __entry->pages_written, __entry->pages_skipped,
__entry->encountered_congestion, __entry->more_io, __entry->encountered_congestion, __entry->more_io,
__entry->no_nrwrite_index_update) __entry->no_nrwrite_index_update,
(unsigned long) __entry->writeback_index)
); );
TRACE_EVENT(ext4_da_write_begin, TRACE_EVENT(ext4_da_write_begin,
......
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