context readahead for concurrent IO take 2
Subsystems (5)
cover context readahead for concurrent IO take 2
Andrew,
The context readahead patches have been floating around for a while. Now that Vladislav confirmed that it helps SCST IO performance, I'd like to push it for more -mm testing outs. I believe this will help many concurrent IO workloads, to name a few: NFS, SCST and even lighttpd.
The patchset is against linux-next, after the earlier filemap and readahead cleanup/fix patches.
Thanks,
Fengguang
PATCH 2/3 readahead: move the random read case to bottom
Split all readahead cases, and move the random one to bottom.
No behavior changes.
This is to prepare for the introduction of context readahead, and make it easy for inserting accounting/tracing points for each case.
--- mm/readahead.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-)
mm/readahead.c +25 -21
@@ -339,33 +339,25 @@ ondemand_readahead(struct address_space unsigned long req_size) {unsigned long max =max_sane_readahead (ra->ra_pages); -pgoff_t prev_offset; -int sequential; + +/* +* start of file +*/ +if (!offset) + goto initial_readahead;/* * It's the expected callback offset, assume sequential access. * Ramp up sizes, and push forward the readahead window. */ -if (offset && (offset == (ra->start + ra->size - ra->async_size) || - offset == (ra->start + ra->size))) { +if ((offset == (ra->start + ra->size - ra->async_size) || + offset == (ra->start + ra->size))) { ra->start += ra->size; ra->size =get_next_ra_size (ra, max); ra->async_size = ra->size; goto readit; } - prev_offset = ra->prev_pos >>PAGE_CACHE_SHIFT ; - sequential = offset - prev_offset <=1UL || req_size > max; - -/* -* Standalone, small read. -* Read as is, and do not pollute the readahead state. -*/ -if (!hit_readahead_marker && !sequential) { -return __do_page_cache_readahead (mapping, filp, - offset, req_size,0 ); - } -/* * Hit a marked page without valid readahead state. * E.g. interleaved reads.
@@ -391,12 +383,24 @@ ondemand_readahead(struct address_space }/* -* It may be one of -* - first read on start of file -* - sequential cache miss -* - oversize random read -* Start readahead for it. +* oversize read */ +if (req_size > max) + goto initial_readahead; + +/* +* sequential cache miss +*/ +if (offset - (ra->prev_pos >>PAGE_CACHE_SHIFT ) <=1UL ) + goto initial_readahead; + +/* +* standalone, small random read +* Read as is, and do not pollute the readahead state. +*/ +return __do_page_cache_readahead (mapping, filp, offset, req_size,0 ); + +initial_readahead: ra->start = offset; ra->size =get_init_ra_size (req_size, max); ra->async_size = ra->size > req_size ? ra->size - req_size : ra->size;
PATCH 1/3 radix-tree: add radix_tree_prev_hole()
In reply to Wu FengguangThe counterpart of radix_tree_next_hole(). To be used by context readahead.
--- include/linux/radix-tree.h | 2 + lib/radix-tree.c | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+)
lib/radix-tree.c +37 -0
@@ -666,6 +666,43 @@ unsigned long radix_tree_next_hole(struc }EXPORT_SYMBOL (radix_tree_next_hole); +/** +* radix_tree_prev_hole - find the prev hole (not-present entry) +* @root: tree root +* @index: index key +* @max_scan: maximum range to search +* +* Search backwards in the range [max(index-max_scan+1, 0), index] +* for the first hole. +* +* Returns: the index of the hole if found, otherwise returns an index +* outside of the set specified (in which case 'index - return >= max_scan' +* will be true). In rare cases of wrap-around, LONG_MAX will be returned. +* +* radix_tree_next_hole may be called under rcu_read_lock. However, like +* radix_tree_gang_lookup, this will not atomically search a snapshot of +* the tree at a single point in time. For example, if a hole is created +* at index 10, then subsequently a hole is created at index 5, +* radix_tree_prev_hole covering both indexes may return 5 if called under +* rcu_read_lock. +*/ +unsigned long radix_tree_prev_hole (struct radix_tree_root *root, +unsigned long index,unsigned long max_scan) +{ +unsigned long i; + +for (i =0 ; i < max_scan; i++) { +if (!radix_tree_lookup (root, index)) +break ; + index--; +if (index ==LONG_MAX ) +break ; + } + +return index; +} +EXPORT_SYMBOL (radix_tree_prev_hole); +static unsigned int __lookup (struct radix_tree_node *slot,void ***results,unsigned long index,unsigned int max_items,unsigned long *next_index)
include/linux/radix-tree.h +2 -0
@@ -167,6 +167,8 @@ radix_tree_gang_lookup_slot(struct radix unsigned long first_index, unsigned int max_items);unsigned long radix_tree_next_hole (struct radix_tree_root *root,unsigned long index,unsigned long max_scan); +unsigned long radix_tree_prev_hole (struct radix_tree_root *root, +unsigned long index,unsigned long max_scan);int radix_tree_preload (gfp_t gfp_mask);void radix_tree_init (void ); void *radix_tree_tag_set(struct radix_tree_root *root,
PATCH 3/3 readahead: introduce context readahead algorithm
In reply to Wu FengguangIntroduce page cache context based readahead algorithm. This is to better support concurrent read streams in general.
RATIONALE
---------
The current readahead algorithm detects interleaved reads in a _passive_ way. Given a sequence of interleaved streams 1,1001,2,1002,3,4,1003,5,1004,1005,6,... By checking for (offset == prev_offset + 1), it will discover the sequentialness between 3,4 and between 1004,1005, and start doing sequential readahead for the individual streams since page 4 and page 1005.
The context readahead algorithm guarantees to discover the sequentialness no matter how the streams are interleaved. For the above example, it will start sequential readahead since page 2 and 1002.
The trick is to poke for page @offset-1 in the page cache when it has no other clues on the sequentialness of request @offset: if the current requenst belongs to a sequential stream, that stream must have accessed page @offset-1 recently, and the page will still be cached now. So if page @offset-1 is there, we can take request @offset as a sequential access.
BENEFICIARIES
-------------
- strictly interleaved reads i.e. 1,1001,2,1002,3,1003,... the current readahead will take them as silly random reads; the context readahead will take them as two sequential streams.
- cooperative IO processes i.e. NFS and SCST
They create a thread pool, farming off (sequential) IO requests to different threads which will be performing interleaved IO.
It was not easy(or possible) to reliably tell from file->f_ra all those cooperative processes working on the same sequential stream, since they will have different file->f_ra instances. And NFSD's file->f_ra is particularly unusable, since their file objects are dynamically created for each request. The nfsd does have code trying to restore the f_ra bits, but not satisfactory.
The new scheme is to detect the sequential pattern via looking up the page cache, which provides one single and consistent view of the pages recently accessed. That makes sequential detection for cooperative processes possible.
USER REPORT
-----------
Vladislav recommends the addition of context readahead as a result of his SCST benchmarks. It leads to 6%~40% performance gains in various cases and achieves
equal performance in others. http://lkml.org/lkml/2009/3/19/239
OVERHEADS
---------
In theory, it introduces one extra page cache lookup per random read. However the below benchmark shows context readahead to be slightly faster, wondering..
Randomly reading 200MB amount of data on a sparse file, repeat 20 times for each block size. The average throughputs are:
original ra context ra gain
4K random reads: 65.561MB/s 65.648MB/s +0.1%
16K random reads: 124.767MB/s 124.951MB/s +0.1%
64K random reads: 162.123MB/s 162.278MB/s +0.1%
--- mm/readahead.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+)
mm/readahead.c +60 -0
@@ -330,6 +330,59 @@ static unsigned long get_next_ra_size(st*/ /* +* Count contiguously cached pages from @offset-1 to @offset-@max, +* this count is a conservative estimation of +* - length of the sequential read sequence, or +* - thrashing threshold in memory tight systems +*/ +static pgoff_t count_history_pages (struct address_space *mapping, +struct file_ra_state *ra, +pgoff_t offset,unsigned long max) +{ +pgoff_t head; + +rcu_read_lock (); + head =radix_tree_prev_hole (&mapping->page_tree, offset -1 , max); +rcu_read_unlock (); + +return offset -1 - head; +} + +/* +* page cache context based read-ahead +*/ +static int try_context_readahead (struct address_space *mapping, +struct file_ra_state *ra, +pgoff_t offset, +unsigned long req_size, +unsigned long max) +{ +pgoff_t size; + + size =count_history_pages (mapping, ra, offset, max); + +/* +* no history pages: +* it could be a random read +*/ +if (!size) +return 0 ; + +/* +* starts from beginning of file: +* it is a strong indication of long-run stream (or whole-file-read) +*/ +if (size >= offset) + size *=2 ; + + ra->start = offset; + ra->size =get_init_ra_size (size + req_size, max); + ra->async_size = ra->size; + +return 1 ; +} + +/* * A minimal readahead algorithm for trivial sequential/random reads. */ static unsigned long
@@ -395,6 +448,13 @@ ondemand_readahead(struct address_space goto initial_readahead;/* +* Query the page cache and look for the traces(cached history pages) +* that a sequential stream would leave behind. +*/ +if (try_context_readahead (mapping, ra, offset, req_size, max)) + goto readit; + +/* * standalone, small random read * Read as is, and do not pollute the readahead state. */
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Quoted 11 lines · Wu Fengguang
Introduce page cache context based readahead algorithm. This is to better support concurrent read streams in general.
/*
+ * Count contiguously cached pages from @offset-1 to @offset-@max,
+ * this count is a conservative estimation of
+ * - length of the sequential read sequence, or
+ * - thrashing threshold in memory tight systems
+ */mm/readahead.c +2
-0+static pgoff_t count_history_pages(struct address_space *mapping, + struct file_ra_state *ra,
mm/readahead.c static unsigned long get_next_ra_size(st · line 349
Quoted 10 lines
mm/readahead.c +10
-0+ pgoff_t offset, unsigned long max) +{ +pgoff_t head; + +rcu_read_lock (); + head =radix_tree_prev_hole (&mapping->page_tree, offset -1 , max); +rcu_read_unlock (); + +return offset -1 - head; +}
Very elegant method! I suspect this will work far better than adding various increasingly more complex heuristics.
Ingo
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Quoted 28 lines · Ingo Molnar
Quoted 11 lines · Wu Fengguang
Introduce page cache context based readahead algorithm. This is to better support concurrent read streams in general.
/*
+ * Count contiguously cached pages from @offset-1 to @offset-@max,
+ * this count is a conservative estimation of
+ * - length of the sequential read sequence, or
+ * - thrashing threshold in memory tight systems
+ */mm/readahead.c +2
-0+static pgoff_t count_history_pages(struct address_space *mapping, + struct file_ra_state *ra,mm/readahead.c static unsigned long get_next_ra_size(st · line 349
Quoted 10 lines
mm/readahead.c +10
-0+ pgoff_t offset, unsigned long max) +{ +pgoff_t head; + +rcu_read_lock (); + head =radix_tree_prev_hole (&mapping->page_tree, offset -1 , max); +rcu_read_unlock (); + +return offset -1 - head; +}Very elegant method! I suspect this will work far better than adding various increasingly more complex heuristics.
Quoted 1 line
Emphatically-Acked-by Ingo Molnar <[email protected]>
Thank you Ingo!
The only pity is that this heuristic can be defeated by some user space program that tries to do aggressive drop-behind via fadvise(DONTNEED) calls. But as long as the drop-behind algorithm be a bit lazy and does not try to squeeze the last page at @offset-1, this patch will work just OK.
The context readahead idea is so fundamental, that a slightly modified algorithm can be used for all kinds of sequential patterns, and it can automatically adapt to the thrashing threshold.
1 if (probe_page(index - 1)) {
2 begin = next_hole(index, max);
3 H = index - prev_hole(index, 2*max);
4 end = index + H;
5 update_window(begin, end);
6 submit_io();
7 }
[=] history [#] current [_] readahead [.] new readahead
==========================#____________..............
1 ^index-1
2 |----------->[begin
3 |<----------- H -----------|
4 |----------- H ----------->]end
5 [ new window ]We didn't do that because we want to
- avoid unnecessary page cache lookups for normal cases
- be more aggressive when thrashings are not a concern
However, readahead thrashings are far more prevalent than one would expect in a FTP/HTTP file streaming server. It can happen in a modern server with 16GB memory, 1Gbps outbound bandwidth and 1MB readahead size, due to the existences of slow streams.
Let's do a coarse calculation. The 8GB inactive_list pages will be cycled in 64Gb/1Gbps=64 seconds. This means an async readahead window must be consumed in 64s, or it will be thrashed. That's a speed of 2048KB/64s=32KB/s. Any client below this speed will create thrashings in the server. In practice, those poor slow clients could amount to half of the total connections(partly because it will take them more time to download anything). The frequent thrashings will in return speedup the LRU cycling/aging speed...
We need a thrashing safe mode which do
- the above modified context readahead algorithm
- conservative ramp up of readahead size
- conservative async readahead size
The main problem is: when shall we switch into the mode?
We can start with aggressive readahead and try to detect readahead thrashings and switch into thrashing safe mode automatically. This will work for non-interleaved reads. However the big file streamer - lighttpd - does interleaved reads. The current data structure is not able to detect most readahead thrashings for lighttpd.
Luckily, the non-resident page tracking facility could help this case. There the thrashed pages with their timing info can be found, based on which we can have some extended context readahead algorithm that could even overcome the drop-behind problem :)
Thanks,
Fengguang
--
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>
Andrew Morton
maintainerRe: [PATCH 1/3] radix-tree: add radix_tree_prev_hole()
In reply to Wu FengguangQuoted 5 lines · On Sun, 12 Apr 2009 15:19:51 +0800 Wu Fengguang <[email protected]> wrote:
lib/radix-tree.c +5
-0+unsigned long radix_tree_prev_hole (struct radix_tree_root *root, +unsigned long index,unsigned long max_scan) +{ +unsigned long i; +
lib/radix-tree.c unsigned long radix_tree_next_hole(struc · line 703
Quoted 10 lines
lib/radix-tree.c +10
-0+for (i =0 ; i < max_scan; i++) { +if (!radix_tree_lookup (root, index)) +break ; + index--; +if (index ==LONG_MAX ) +break ; + } + +return index; +}
argh. This is about as inefficient as we could possibly make it :(
Really, this function should dive into radix-tree internals and walk individual radix_tree_node.slots[] arrays. And heck, it can peek at radix_tree_node.count and _bypass_ entire nodes, too.
Re: [PATCH 1/3] radix-tree: add radix_tree_prev_hole()
Quoted 11 lines · Andrew Morton
Quoted 9 lines · On Sun, 12 Apr 2009 15:19:51 +0800 Wu Fengguang <[email protected]> wrote:
lib/radix-tree.c +9
-0+unsigned long radix_tree_prev_hole (struct radix_tree_root *root, +unsigned long index,unsigned long max_scan) +{ +unsigned long i; + +for (i =0 ; i < max_scan; i++) { +if (!radix_tree_lookup (root, index)) +break ; + index--;
Quoted 8 lines
lib/radix-tree.c unsigned long radix_tree_next_hole(struc · line 703
Quoted 6 lines
lib/radix-tree.c +6
-0+if (index ==LONG_MAX ) +break ; + } + +return index; +}argh. This is about as inefficient as we could possibly make it :(
Right, a dumb loop!
Quoted 3 lines
Really, this function should dive into radix-tree internals and walk individual radix_tree_node.slots[] arrays. And heck, it can peek at radix_tree_node.count and _bypass_ entire nodes, too.
Good idea! In fact I'm planning such a smart version. It will be using radix_tree_lookup_slot() to access the ->count member, in order to check if the whole slot can be bypassed.
radix_tree_next_hole() is another optimization candidate. But that will be a post 2.6.30 stuff.
The current dumb-but-obvious-right version is OK for 2.6.30, because in most radix_tree_prev_hole() invocations the actual loop count will be merely 1.
Thanks,
Fengguang
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
In reply to a message not in the archiveQuoted 18 lines · Andi Kleen
Quoted 8 lines · Wu Fengguang
The context readahead algorithm guarantees to discover the sequentialness no matter how the streams are interleaved. For the above example, it will start sequential readahead since page 2 and 1002.
The trick is to poke for page @offset-1 in the page cache when it has no other clues on the sequentialness of request @offset: if the current requenst belongs to a sequential stream, that stream must have accessed page @offset-1 recently,
Really? The page could be just randomly cached from some previous IO, right? I don't think you can detect that right? [maybe you could play some games with the LRU list, but that would be likely not O(1)]
I wonder if this could cause the prefetcher to "switch gears" suddenly when it first walks a long stretch of non cached file data and then suddenly hits a cached page.
Quoted 8 lines
One possible way around this would be to add a concept of "page generation". e.g. use a few bits of page flags. keep a global generation count that increases slowly (let's say every few seconds) Mark the page with the current generation count when the prefetcher takes it. When doing your algorithm check the count first and only use pages with a recent count.
Not sure if it's a real problem or not.
Good catch Andi!
I'll list some possible situations. I guess you are referring to (2.3)?
1) page at @offset-1 is present, and it may be a trace of:
1.1) (interleaved) sequential reads: we catch it, bingo!
1.2) clustered random reads: readahead will be mostly favorable
If page @offset-1 exists, it means there are two references that come close in both space(distance < one readahead window) and time (distance < one LRU scan cycle). So it's a good indication that some clustered random reads are occurring in the area around @offset.
I have did many experiments on the performance impact of readahead on clustered random reads - the results are very encouraging. The tests covered different random read densities, random read sizes, as well as different thrashing conditions (by varying the dataset:memory ratios). There are hardly any performance loss(2% for the worst case), but the gain can be as large as 300%!
Some numbers, curves and analysis can be found in the attached slides and paper:
- readahead performance slides: Page 23-26: Clustered random reads
- readahead framework paper: Page 7, Section 4.3: Random reads
The recent readahead addition to mmap reads makes another vivid example of the stated "readahead is good for clustered random reads" principle. The readahead's side effect for the random references by executables are very good:
- major faults reduced by 1/3
- mmap IO numbers reduced by 1/4
- with no obvious overheads
But as always, one can fake a workload to totally defeat the readahead heuristics ;-)
2) page at @offset-1 is not present (for a sequential stream)
2.1) aggressive user space drop behind: fixable nuisance
The user space could be doing fadvise(offset-1, DONTNEED), which will drop the history hint required to enable context readahead. But I guess when the administrator/developer noticed its impact - performance dropped instead of increased - he can easily fix it up to do fadvise(offset-2, DONTNEED), or to manage its own readahead via fadvise(WILLNEED).
So this is an nuisance but fixable situation.
2.2) readahead thrashing: not handled for now
We don't handle this for now. For now the behavior is to stop readahead and possibly restart the readahead window rampup process.
2.3) readahead cache hits: rare case and the impact is temporary
The page at @offset-1 does get referenced by this stream, but it's created by someone else at some distant time ago. The page at @offset-1 may be lifted to active lru by this second reference, or too late and get reclaimed - by the time we reference page @offset.
Normally its a range of cached pages. We are a) either walking inside the range and enjoying the cache hits, b) or we walk out of it and restart readahead by ourself, c) or the range of cached pages get reclaimed while we are walking on them, and hence cannot find page @offset-1.
Obviously (c) is rare and temporary and is the main cause of (2.3). As soon as we goto the next page at @offset+1, we'll its 'previous' page at @offset to be cached(it is created by us!). So the context readahead starts working again - it's merely delayed by one page :-)
Thanks,
Fengguang
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Quoted 1 line
I'll list some possible situations. I guess you are referring to (2.3)?
Yep. Thanks for the detailed analysis.
Quoted 12 lines
2.3) readahead cache hits: rare case and the impact is temporary
The page at @offset-1 does get referenced by this stream, but it's created by someone else at some distant time ago. The page at @offset-1 may be lifted to active lru by this second reference, or too late and get reclaimed - by the time we reference page @offset.
Normally its a range of cached pages. We are a) either walking inside the range and enjoying the cache hits, b) or we walk out of it and restart readahead by ourself, c) or the range of cached pages get reclaimed while we are walking on them, and hence cannot find page @offset-1.
Quoted 4 lines
Obviously (c) is rare and temporary and is the main cause of (2.3). As soon as we goto the next page at @offset+1, we'll its 'previous' page at @offset to be cached(it is created by us!). So the context readahead starts working again - it's merely delayed by one page :-)
Thanks. The question is how much performance impact this has on the stream that is readaheaded. I guess it would be only a smaller "hickup", with some luck hidden by the block level RA?
The other question would be if it could cause the readahead code to do a lot of unnecessary work, but your answer seems to be "no". Fine.
I think the concept is sound.
-Andi
Signature · 7 lines
[email protected] -- Speaking for myself only. -- 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>
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Quoted 3 lines · Andi Kleen
Quoted 1 line
I'll list some possible situations. I guess you are referring to (2.3)?
Yep. Thanks for the detailed analysis.
You are welcome :-)
Quoted 12 lines
Quoted 12 lines
2.3) readahead cache hits: rare case and the impact is temporary
The page at @offset-1 does get referenced by this stream, but it's created by someone else at some distant time ago. The page at @offset-1 may be lifted to active lru by this second reference, or too late and get reclaimed - by the time we reference page @offset.
Normally its a range of cached pages. We are a) either walking inside the range and enjoying the cache hits, b) or we walk out of it and restart readahead by ourself, c) or the range of cached pages get reclaimed while we are walking on them, and hence cannot find page @offset-1.
Quoted 8 lines
Quoted 4 lines
Obviously (c) is rare and temporary and is the main cause of (2.3). As soon as we goto the next page at @offset+1, we'll its 'previous' page at @offset to be cached(it is created by us!). So the context readahead starts working again - it's merely delayed by one page :-)
Thanks. The question is how much performance impact this has on the stream that is readaheaded. I guess it would be only a smaller "hickup", with some luck hidden by the block level RA?
Yes there will be hickup: whenever the stream walks out of the current cached page range(or the pages get reclaimed while we are walking in it), there will be a 1-page read, followed by a 4-page readahead, and then 8, 16, ... page sized readahead, i.e. a readahead window rampup process.
That's assuming we are doing 1-page reads. For large sendfile() calls, the readahead size will be instantly restored to its full size on the first cache miss.
Quoted 2 lines
The other question would be if it could cause the readahead code to do a lot of unnecessary work, but your answer seems to be "no". Fine.
Right. The readahead will automatically be turned off inside the cached page ranges, and restarted after walking out of it.
Quoted 1 line
I think the concept is sound.
^_^ Thanks for your appreciation!
Best Regards,
Fengguang
--
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>
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Quoted 27 lines · Wu Fengguang
Quoted 3 lines · Andi Kleen
Quoted 1 line
I'll list some possible situations. I guess you are referring to (2.3)?
Yep. Thanks for the detailed analysis.
You are welcome :-)
Quoted 20 lines
Quoted 12 lines
2.3) readahead cache hits: rare case and the impact is temporary
The page at @offset-1 does get referenced by this stream, but it's created by someone else at some distant time ago. The page at @offset-1 may be lifted to active lru by this second reference, or too late and get reclaimed - by the time we reference page @offset.
Normally its a range of cached pages. We are a) either walking inside the range and enjoying the cache hits, b) or we walk out of it and restart readahead by ourself, c) or the range of cached pages get reclaimed while we are walking on them, and hence cannot find page @offset-1.
Quoted 4 lines
Obviously (c) is rare and temporary and is the main cause of (2.3). As soon as we goto the next page at @offset+1, we'll its 'previous' page at @offset to be cached(it is created by us!). So the context readahead starts working again - it's merely delayed by one page :-)
Thanks. The question is how much performance impact this has on the stream that is readaheaded. I guess it would be only a smaller "hickup", with some luck hidden by the block level RA?
No the block level RA wont help here, because there are no disk accesses at all for the cached pages before @offset: the disk RA have absolutely no idea on where the IO for page @offset originates from ;-)
Quoted 15 lines
Yes there will be hickup: whenever the stream walks out of the current cached page range(or the pages get reclaimed while we are walking in it), there will be a 1-page read, followed by a 4-page readahead, and then 8, 16, ... page sized readahead, i.e. a readahead window rampup process.
That's assuming we are doing 1-page reads. For large
sendfile()calls, the readahead size will be instantly restored to its full size on the first cache miss.Quoted 2 lines
The other question would be if it could cause the readahead code to do a lot of unnecessary work, but your answer seems to be "no". Fine.
Right. The readahead will automatically be turned off inside the cached page ranges, and restarted after walking out of it.
Quoted 7 lines
Quoted 1 line
I think the concept is sound.
^_^ Thanks for your appreciation!
Best Regards,
Fengguang
--
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>
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
In reply to Wu FengguangQuoted 31 lines · Wu Fengguang
Introduce page cache context based readahead algorithm. This is to better support concurrent read streams in general.
RATIONALE
---------
The current readahead algorithm detects interleaved reads in a _passive_ way. Given a sequence of interleaved streams 1,1001,2,1002,3,4,1003,5,1004,1005,6,... By checking for (offset ==prev_offset+ 1), it will discover the sequentialness between 3,4 and between 1004,1005, and start doing sequential readahead for the individual streams since page 4 and page 1005.The context readahead algorithm guarantees to discover the sequentialness no matter how the streams are interleaved. For the above example, it will start sequential readahead since page 2 and 1002.
The trick is to poke for page @offset-1 in the page cache when it has no other clues on the sequentialness of request @offset: if the current requenst belongs to a sequential stream, that stream must have accessed page @offset-1 recently, and the page will still be cached now. So if page @offset-1 is there, we can take request @offset as a sequential access.
BENEFICIARIES
-------------
- strictly interleaved reads i.e. 1,1001,2,1002,3,1003,... the current readahead will take them as silly random reads; the context readahead will take them as two sequential streams.- cooperative IO processes i.e. NFS and SCST
They create a thread pool, farming off (sequential) IO requests to different threads which will be performing interleaved IO.
Quoted 5 lines
It was not easy(or possible) to reliably tell from
file->f_raall those cooperative processes working on the same sequential stream, since they will have differentfile->f_rainstances. And NFSD'sfile->f_rais particularly unusable, since their file objects are dynamically created for each request. The nfsd does have code trying to restore thef_rabits, but not satisfactory.
Hi, Wu,
I tested out your patches. Below are some basic iozone numbers for a single NFS client reading a file. The iozone command line is:
iozone -s 2000000 -r 64 -f /mnt/test/testfile -i 1 -w
The file system is unmounted after each run to flush the cache. The numbers below reflect only a single run each. The file system was also unmounted on the NFS client after each run.
KEY
---
vanilla: 2.6.30-rc1
readahead: 2.6.30-rc1 + your 10 readahead patches
context readahead: 2.6.30-rc1 + your 10 readahead patches + the 3
context readahead patches.
nfsd's: number of NFSD threads on the server
I'll note that the cfq in 2.6.30-rc1 is crippled, and that Jens has a patch posted that makes the numbers look at least a little better, but that's immaterial to this discussion, I think.
vanilla
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------
cfq | 43127 | 22354 | 20858 | 21179 deadline| 43732 | 68059 | 76659 | 83231
readahead
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------
cfq | 42471 | 21913 | 21252 | 20979 deadline| 42801 | 70158 | 82068 | 82406
context readahead
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------
cfq | 42827 | 21882 | 20678 | 21508 deadline| 43040 | 71173 | 82407 | 86583
Cheers,
Jeff
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Quoted 39 lines · Jeff Moyer
Quoted 31 lines · Wu Fengguang
Introduce page cache context based readahead algorithm. This is to better support concurrent read streams in general.
RATIONALE
---------
The current readahead algorithm detects interleaved reads in a _passive_ way. Given a sequence of interleaved streams 1,1001,2,1002,3,4,1003,5,1004,1005,6,... By checking for (offset ==prev_offset+ 1), it will discover the sequentialness between 3,4 and between 1004,1005, and start doing sequential readahead for the individual streams since page 4 and page 1005.The context readahead algorithm guarantees to discover the sequentialness no matter how the streams are interleaved. For the above example, it will start sequential readahead since page 2 and 1002.
The trick is to poke for page @offset-1 in the page cache when it has no other clues on the sequentialness of request @offset: if the current requenst belongs to a sequential stream, that stream must have accessed page @offset-1 recently, and the page will still be cached now. So if page @offset-1 is there, we can take request @offset as a sequential access.
BENEFICIARIES
-------------
- strictly interleaved reads i.e. 1,1001,2,1002,3,1003,... the current readahead will take them as silly random reads; the context readahead will take them as two sequential streams.- cooperative IO processes i.e. NFS and SCST
They create a thread pool, farming off (sequential) IO requests to different threads which will be performing interleaved IO.Quoted 5 lines
It was not easy(or possible) to reliably tell from
file->f_raall those cooperative processes working on the same sequential stream, since they will have differentfile->f_rainstances. And NFSD'sfile->f_rais particularly unusable, since their file objects are dynamically created for each request. The nfsd does have code trying to restore thef_rabits, but not satisfactory.
Quoted 6 lines
Hi, Wu,
I tested out your patches. Below are some basic iozone numbers for a single NFS client reading a file. The iozone command line is:
iozone -s 2000000 -r 64 -f
/mnt/test/testfile-i 1 -w
Jeff, thank you very much for the testing out!
Quoted 11 lines
The file system is unmounted after each run to flush the cache. The numbers below reflect only a single run each. The file system was also unmounted on the NFS client after each run.
KEY
---
vanilla: 2.6.30-rc1
readahead: 2.6.30-rc1 + your 10 readahead patches
context readahead: 2.6.30-rc1 + your 10 readahead patches + the 3
context readahead patches.
nfsd's: number of NFSD threads on the server
I guess you are applying the readahead patches to the server side?
What's the NFS mount options and client/server side readahead size? The context readahead is pretty sensible to these parameters.
Quoted 18 lines
I'll note that the cfq in 2.6.30-rc1 is crippled, and that Jens has a patch posted that makes the numbers look at least a little better, but that's immaterial to this discussion, I think.
vanilla
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------cfq | 43127 | 22354 | 20858 | 21179 deadline| 43732 | 68059 | 76659 | 83231readahead
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------cfq | 42471 | 21913 | 21252 | 20979 deadline| 42801 | 70158 | 82068 | 82406
Quoted 6 lines
context readahead
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------cfq | 42827 | 21882 | 20678 | 21508 deadline| 43040 | 71173 | 82407 | 86583
Let me transform them into relative numbers:
A B C A..B A..C cfq-1 43127 42471 42827 -1.5% -0.7% cfq-2 22354 21913 21882 -2.0% -2.1% cfq-4 20858 21252 20678 +1.9% -0.9% cfq-8 21179 20979 21508 -0.9% +1.6%
deadline-1 43732 42801 43040 -2.1% -1.6% deadline-2 68059 70158 71173 +3.1% +4.6% deadline-4 76659 82068 82407 +7.1% +7.5% deadline-8 83231 82406 86583 -1.0% +4.0%
Summaries:
1) the overall numbers are slightly negative for CFQ and looks better with deadline.
Anyway we have the io context problem for CFQ. And I'm planning to dive into the CFQ code and your patch on that :-)
2) the single thread case performance consistently dropped by 1-2%.
It seems not related to the behavior changes introduced by the mmap readahead patches and context readahead patches. And looks more like some overheads created by the code reorganization and the patch "readahead: apply max_sane_readahead() limit in ondemand_readahead()" which adds a bit overhead with the call max_sane_readahead().
I'll try to root cause it.
Thanks again for the numbers!
Regards,
Fengguang
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
Hi, Fengguang,
Quoted 19 lines · Wu Fengguang
Quoted 4 lines
I tested out your patches. Below are some basic iozone numbers for a single NFS client reading a file. The iozone command line is:
iozone -s 2000000 -r 64 -f
/mnt/test/testfile-i 1 -wJeff, thank you very much for the testing out!
Quoted 11 lines
The file system is unmounted after each run to flush the cache. The numbers below reflect only a single run each. The file system was also unmounted on the NFS client after each run.
KEY
---
vanilla: 2.6.30-rc1
readahead: 2.6.30-rc1 + your 10 readahead patches
context readahead: 2.6.30-rc1 + your 10 readahead patches + the 3
context readahead patches.
nfsd's: number of NFSD threads on the server
Quoted 1 line
I guess you are applying the readahead patches to the server side?
That's right.
Quoted 2 lines
What's the NFS mount options and client/server side readahead size? The context readahead is pretty sensible to these parameters.
Default options everywhere.
Quoted 33 lines
Quoted 18 lines
I'll note that the cfq in 2.6.30-rc1 is crippled, and that Jens has a patch posted that makes the numbers look at least a little better, but that's immaterial to this discussion, I think.
vanilla
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------cfq | 43127 | 22354 | 20858 | 21179 deadline| 43732 | 68059 | 76659 | 83231readahead
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------cfq | 42471 | 21913 | 21252 | 20979 deadline| 42801 | 70158 | 82068 | 82406Quoted 6 lines
context readahead
nfsd's | 1 | 2 | 4 | 8
--------+---------------+-------+------cfq | 42827 | 21882 | 20678 | 21508 deadline| 43040 | 71173 | 82407 | 86583Let me transform them into relative numbers:
A B C A..B A..C cfq-1 43127 42471 42827 -1.5% -0.7% cfq-2 22354 21913 21882 -2.0% -2.1% cfq-4 20858 21252 20678 +1.9% -0.9% cfq-8 21179 20979 21508 -0.9% +1.6%
Quoted 8 lines
deadline-1 43732 42801 43040 -2.1% -1.6% deadline-2 68059 70158 71173 +3.1% +4.6% deadline-4 76659 82068 82407 +7.1% +7.5% deadline-8 83231 82406 86583 -1.0% +4.0%Summaries:
1) the overall numbers are slightly negative for CFQ and looks better with deadline.
The variance is probably 1-2%. I'll try to quantify that for you.
Quoted 2 lines
Anyway we have the io context problem for CFQ. And I'm planning to dive into the CFQ code and your patch on that :-)
Jens already reworked the patch and included it in his for-linus branch of the block tree. So, you can start there. ;-)
Quoted 11 lines
2) the single thread case performance consistently dropped by 1-2%.
It seems not related to the behavior changes introduced by the mmap readahead patches and context readahead patches. And looks more like some overheads created by the code reorganization and the patch "readahead: apply
max_sane_readahead()limit inondemand_readahead()" which adds a bit overhead with the callmax_sane_readahead().I'll try to root cause it.
Thanks again for the numbers!
No problem.
Cheers,
Jeff
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
In reply to Wu FengguangQuoted 74 lines · Wu Fengguang, on 04/12/2009 04:35 PM wrote:
Quoted 24 lines · Ingo Molnar
Quoted 10 lines · Wu Fengguang
Introduce page cache context based readahead algorithm. This is to better support concurrent read streams in general.
/*
+ * Count contiguously cached pages from @offset-1 to @offset-@max,
+ * this count is a conservative estimation of
+ * - length of the sequential read sequence, or
+ * - thrashing threshold in memory tight systems
+ */mm/readahead.c +2
-0+static pgoff_t count_history_pages(struct address_space *mapping, + struct file_ra_state *ra,mm/readahead.c static unsigned long get_next_ra_size(st · line 349
Quoted 10 lines
mm/readahead.c +10
-0+ pgoff_t offset, unsigned long max) +{ +pgoff_t head; + +rcu_read_lock (); + head =radix_tree_prev_hole (&mapping->page_tree, offset -1 , max); +rcu_read_unlock (); + +return offset -1 - head; +}Very elegant method! I suspect this will work far better than adding various increasingly more complex heuristics.
Quoted 1 line
Emphatically-Acked-by Ingo Molnar <[email protected]>Thank you Ingo!
The only pity is that this heuristic can be defeated by some user space program that tries to do aggressive drop-behind via fadvise(DONTNEED) calls. But as long as the drop-behind algorithm be a bit lazy and does not try to squeeze the last page at @offset-1, this patch will work just OK.
The context readahead idea is so fundamental, that a slightly modified algorithm can be used for all kinds of sequential patterns, and it can automatically adapt to the thrashing threshold.
1 if (probe_page(index - 1)) { 2 begin = next_hole(index, max); 3 H = index - prev_hole(index, 2*max); 4 end = index + H; 5 update_window(begin, end); 6 submit_io(); 7 } [=] history [#] current [_] readahead [.] new readahead ==========================#____________.............. 1 ^index-1 2 |----------->[begin 3 |<----------- H -----------| 4 |----------- H ----------->]end 5 [ new window ]We didn't do that because we want to
- avoid unnecessary page cache lookups for normal cases
- be more aggressive when thrashings are not a concernHowever, readahead thrashings are far more prevalent than one would expect in a FTP/HTTP file streaming server. It can happen in a modern server with 16GB memory, 1Gbps outbound bandwidth and 1MB readahead size, due to the existences of slow streams.
Let's do a coarse calculation. The 8GB
inactive_listpages will be cycled in 64Gb/1Gbps=64 seconds. This means an async readahead window must be consumed in 64s, or it will be thrashed. That's a speed of 2048KB/64s=32KB/s. Any client below this speed will create thrashings in the server. In practice, those poor slow clients could amount to half of the total connections(partly because it will take them more time to download anything). The frequent thrashings will in return speedup the LRU cycling/aging speed...
Quoted 6 lines
We need a thrashing safe mode which do
- the above modified context readahead algorithm
- conservative ramp up of readahead size
- conservative async readahead sizeThe main problem is: when shall we switch into the mode?
More I think about an ideal readahead, more it looks like page cache should also keep fairness between its users, similarly as it's done for CPU (CFS) and disk (CFQ). A slow user should have a chance to use its chunk the cache in face of too fast thrasher.
The main problems with it are to define what "user" is and how to implement the fairness in an acceptably simple way.
Maybe something like that: "user" is IO context (or IO stream?) and, if there would be a need to get some pages for "user" A, pages belonging to other "users" would be evicted with additional wight W, so A's pages would be preferred during eviction.
Just my 0.05c.
Quoted 11 lines
We can start with aggressive readahead and try to detect readahead thrashings and switch into thrashing safe mode automatically. This will work for non-interleaved reads. However the big file streamer - lighttpd - does interleaved reads. The current data structure is not able to detect most readahead thrashings for lighttpd.
Luckily, the non-resident page tracking facility could help this case. There the thrashed pages with their timing info can be found, based on which we can have some extended context readahead algorithm that could even overcome the drop-behind problem :)
Quoted 8 lines
Thanks,
Fengguang
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
--
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>
Re: [PATCH 3/3] readahead: introduce context readahead algorithm
In reply to Jeff MoyerHi Jeff,
I did some more NFS readahead tests. Judging from your and mine tests, I can say that the context readahead is safe for trivial NFS workloads :-) It is behaving in the expected way, and the overheads, if any, are close enough to the fluctuating margin.
Quoted 23 lines · Jeff Moyer
Hi, Fengguang,
Quoted 19 lines · Wu Fengguang
Quoted 4 lines
I tested out your patches. Below are some basic iozone numbers for a single NFS client reading a file. The iozone command line is:
iozone -s 2000000 -r 64 -f
/mnt/test/testfile-i 1 -wJeff, thank you very much for the testing out!
Quoted 11 lines
The file system is unmounted after each run to flush the cache. The numbers below reflect only a single run each. The file system was also unmounted on the NFS client after each run.
KEY
---
vanilla: 2.6.30-rc1
readahead: 2.6.30-rc1 + your 10 readahead patches
context readahead: 2.6.30-rc1 + your 10 readahead patches + the 3
context readahead patches.
nfsd's: number of NFSD threads on the server
Quoted 8 lines
Quoted 1 line
I guess you are applying the readahead patches to the server side?
That's right.
Quoted 2 lines
What's the NFS mount options and client/server side readahead size? The context readahead is pretty sensible to these parameters.
Default options everywhere.
The default options observed in my test platforms:
- client: CFQ, kernel 2.6.30-rc3 + linux-2.6-block.git for linus
- server: CFQ, kernel 2.6.30-rc2-next-20090417
is
- rsize=256k
- NFS readahead size=3840k (= 256k * 15)
- sda readahead size=128k
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
I'll note that the cfq in 2.6.30-rc1 is crippled, and that Jens has a patch posted that makes the numbers look at least a little better, but that's immaterial to this discussion, I think.
[snip]
Quoted 13 lines
Quoted 13 lines
Let me transform them into relative numbers:
A B C A..B A..C cfq-1 43127 42471 42827 -1.5% -0.7% cfq-2 22354 21913 21882 -2.0% -2.1% cfq-4 20858 21252 20678 +1.9% -0.9% cfq-8 21179 20979 21508 -0.9% +1.6%deadline-1 43732 42801 43040 -2.1% -1.6% deadline-2 68059 70158 71173 +3.1% +4.6% deadline-4 76659 82068 82407 +7.1% +7.5% deadline-8 83231 82406 86583 -1.0% +4.0%
Quoted 5 lines
Quoted 3 lines
Summaries:
1) the overall numbers are slightly negative for CFQ and looks better with deadline.The variance is probably 1-2%. I'll try to quantify that for you.
I tried to measure the overheads, here is the approach:
- random read(4K) syscalls on a huge sparse file over NFS
- server side readahead size=1M, otherwise all default options
The -0.1%, +0.5% differences in time are close enough to the variance.
vanilla +max_sane_readahead() +mmap readahead
run-1 77.01s 77.18 77.96s
run-2 77.18s 77.53 77.76s
run-3 77.93s 77.57 77.84s
run-4 77.76s 78.16s
run-5 77.55s 77.76s
run-6 77.90s
avg 77.486 77.427 77.897
diff% -0.1% +0.5%Quoted 5 lines
Quoted 2 lines
Anyway we have the io context problem for CFQ. And I'm planning to dive into the CFQ code and your patch on that :-)
Jens already reworked the patch and included it in his for-linus branch of the block tree. So, you can start there. ;-)
Good news. I'm running with it :-)
Quoted 9 lines
Quoted 9 lines
2) the single thread case performance consistently dropped by 1-2%.
It seems not related to the behavior changes introduced by the mmap readahead patches and context readahead patches. And looks more like some overheads created by the code reorganization and the patch "readahead: apply
max_sane_readahead()limit inondemand_readahead()" which adds a bit overhead with the callmax_sane_readahead().I'll try to root cause it.
Then I go on to test sequential reads on real files over NFS.
Again the differences are small enough.
vanilla +mmap&context readahead diff% nfsd=1 28.875s 28.770s -0.4% nfsd=8 42.533s 42.255s -0.7%
For the single nfsd case, the readahead sequence is perfect and exactly the same before/after the context readahead patch:
[ 60.542986] readahead-initial0(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=0+64, ra=0+128-64, async=0) = 128 [ 60.573652] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=64+32, ra=128+256-256, async=1) = 2 56 [ 60.590312] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=128+32, ra=384+256-256, async=1) = 256 [ 60.652863] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=384+32, ra=640+256-256, async=1) = 256 [ 60.713916] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=640+32, ra=896+256-256, async=1) = 256 [ 60.776168] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=896+32, ra=1152+256-256, async=1) = 256 [ 60.837423] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=1152+32, ra=1408+256-256, async=1) = 256 [ 60.899360] readahead-subsequent(pid=3124(nfsd), dev=08:02(sda2), ino=129(vmlinux-2.6.29), req=1408+32, ra=1664+256-256, async=1)
= 256
Thanks,
Fengguang