* Re: raid0 vs. mkfs [not found] ` <33bb250a-4dfd-0acc-9958-30fdac10918c@scylladb.com> @ 2016-12-07 11:50 ` Coly Li 2016-12-07 12:03 ` Coly Li 2016-12-07 16:59 ` Shaohua Li 0 siblings, 2 replies; 10+ messages in thread From: Coly Li @ 2016-12-07 11:50 UTC (permalink / raw) To: Avi Kivity, NeilBrown; +Cc: linux-raid, linux-block [-- Attachment #1: Type: text/plain, Size: 3401 bytes --] On 2016/11/30 上午6:45, Avi Kivity wrote: > On 11/29/2016 11:14 PM, NeilBrown wrote: [snip] >>> So I disagree that all the work should be pushed to the merging layer. >>> It has less information to work with, so the fewer decisions it has to >>> make, the better. >> I think that the merging layer should be as efficient as it reasonably >> can be, and particularly should take into account plugging. This >> benefits all callers. > > Yes, but plugging does not mean "please merge anything you can until the > unplug". > >> If it can be demonstrated that changes to some of the upper layers bring >> further improvements with acceptable costs, then certainly it is good to >> have those too. > > Generating millions of requests only to merge them again is > inefficient. It happens in an edge case (TRIM of the entirety of a very > large RAID), but it already caused on user to believe the system > failed. I think the system should be more robust than that. Neil, As my understand, if a large discard bio received by raid0_make_request(), for example it requests to discard chunk 1 to 24 on a raid0 device built by 4 SSDs. This large discard bio will be split and written to each SSD as the following layout, SSD1: C1,C5,C9,C13,C17,C21 SSD2: C2,C6,C10,C14,C18,C22 SSD3: C3,C7,C11,C15,C19,C23 SSD4: C4,C8,C12,C16,C20,C24 Current raid0 code will call generic_make_request() for 24 times for each split bio. But it is possible to calculate the final layout of each split bio, so we can combine all the bios into four per-SSD large bio, like this, bio1 (on SSD1): C{1,5,9,13,17,21} bio2 (on SSD2): C{2,6,10,14,18,22} bio3 (on SSD3): C{3,7,11,15,19,23} bio4 (on SSD4): C{4,8,12,16,20,24} Now we only need to call generic_make_request() for 4 times. Rebuild the per-device discard bios is more efficient in raid0 code then in block layer. There are some reasons that I know, - there are splice timeout, block layer cannot merge all split bio into one large bio before time out. - rebuilt per-device bio in raid0 is just by a few calculation, block layer does merge on queue with list operations, it is slower. - raid0 code knows its on disk layout, so rebuild per-device bio is possible here. block layer has no idea on raid0 layout, it can only do request merge. Avi, I compose a prototype patch, the code is not simple, indeed it is quite complicated IMHO. I do a little research, some NVMe SSDs support whole device size DISCARD, also I observe mkfs.xfs sends out a raid0 device size DISCARD bio to block layer. But raid0_make_request() only receives 512KB size DISCARD bio, block/blk-lib.c:__blkdev_issue_discard() splits the original large bio into 512KB small bios, the limitation is from q->limits.discard_granularity. At this moment, I don't know why a q->limits.discard_granularity is 512KB even the underlying SSD supports whole device size discard. We also need to fix q->limits.discard_granularity, otherwise block/blk-lib.c:__blkdev_issue_discard() still does an inefficient loop to split the original large discard bio into smaller ones and sends them to raid0 code by next_bio(). I also CC this email to linux-block@vger.kernel.org to ask for help. My question is, if a NVMe SSD supports whole-device-size DISCARD, is q->limits.discard_granularity still necessary ? Here I also attach my prototype patch as a proof of concept, it is runnable with Linux 4.9-rc7. Coly [-- Attachment #2: raid0_handle_large_discard_bio.patch --] [-- Type: text/plain, Size: 9329 bytes --] Subject: optimization for large size DISCARD bio by per-device bios This is a very early prototype, still needs more block layer code modification to make it work. Current upstream raid0_make_request() only handles TRIM/DISCARD bio by chunk size, it meams for large raid0 device built by SSDs will call million times generic_make_request() for the split bio. This patch tries to combine small bios into large one if they are on same real device and continuous on this real device, then send the combined large bio to underlying device by single call to generic_make_request(). For example, use mkfs.xfs to trim a raid0 device built with 4 x 3TB NVMeSSD, current upstream raid0_make_request() will call generic_make_request() 5.7 million times, with this patch only 4 calls to generic_make_request() is required. This patch won't work in real world, because in block/blk-lib.c: __blkdev_issue_discard() the original large bio will be split into smaller ones by restriction of discard_granularity. If some day SSD supports whole device sized discard_granularity, it will be very interesting then... The basic idea is, if a large discard bio received by raid0_make_request(), for example it requests to discard chunk 1 to 24 on a raid0 device built by 4 SSDs. This large discard bio will be split and written to each SSD as the following layout, SSD1: C1,C5,C9,C13,C17,C21 SSD2: C2,C6,C10,C14,C18,C22 SSD3: C3,C7,C11,C15,C19,C23 SSD4: C4,C8,C12,C16,C20,C24 Current raid0 code will call generic_make_request() for 24 times for each split bio. But it is possible to calculate the final layout of each split bio, so we can combine all the bios into four per-SSD large bio, like this, bio1 (on SSD1): C{1,5,9,13,17,21} bio2 (on SSD2): C{2,6,10,14,18,22} bio3 (on SSD3): C{3,7,11,15,19,23} bio4 (on SSD4): C{4,8,12,16,20,24} Now we only need to call generic_make_request() for 4 times. The code is not simple, I need more time to write text to complain how it works. Currently you can treat it as a proof of concept. Signed-off-by: Coly Li <colyli@suse.de> --- drivers/md/raid0.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 258986a..73c5fac 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -452,6 +452,225 @@ static inline int is_io_in_chunk_boundary(struct mddev *mddev, } } + +struct bio_record { + sector_t bi_sector; + unsigned long sectors; + struct md_rdev *rdev; +}; + +static void handle_discard_request(struct mddev *mddev, struct bio *bio) +{ + struct bio_record *recs = NULL; + struct bio *split; + struct r0conf *conf = mddev->private; + sector_t sectors, sector; + struct strip_zone *first_zone; + int zone_idx; + sector_t zone_start, zone_end; + int nr_strip_zones = conf->nr_strip_zones; + int disks; + int first_rdev_idx = -1, rdev_idx; + struct md_rdev *first_rdev; + unsigned int chunk_sects = mddev->chunk_sectors; + + sector = bio->bi_iter.bi_sector; + first_zone = find_zone(conf, §or); + first_rdev = map_sector(mddev, first_zone, sector, §or); + + sectors = chunk_sects - + (likely(is_power_of_2(chunk_sects)) + ? (sector & (chunk_sects - 1)) + : sector_div(sector, chunk_sects)); + + /* if bio size is not exceed a chunk boundary, + * simply handle it here. + */ + if (sectors >= bio_sectors(bio)) { + bio->bi_bdev = first_rdev->bdev; + bio->bi_iter.bi_sector = sector + first_zone->dev_start + + first_rdev->data_offset; + if (unlikely(!blk_queue_discard(bdev_get_queue(bio->bi_bdev)))) + /* Just ignore it */ + bio_endio(bio); + else + generic_make_request(bio); + return; + } + + /* bio is large enough to be split, allocate recs firstly */ + disks = mddev->raid_disks; + recs = kcalloc(disks, sizeof(struct bio_record), GFP_NOIO); + if (recs == NULL) { + printk(KERN_ERR "md/raid0:%s: failed to allocate memory " \ + "for bio_record", mdname(mddev)); + bio->bi_error = -ENOMEM; + bio_endio(bio); + return; + } + + zone_idx = first_zone - conf->strip_zone; + for (rdev_idx = 0; rdev_idx < first_zone->nb_dev; rdev_idx++) { + struct md_rdev *rdev; + + rdev = conf->devlist[zone_idx * disks + rdev_idx]; + recs[rdev_idx].rdev = rdev; + if (rdev == first_rdev) + first_rdev_idx = rdev_idx; + } + + /* Restore due to sector_div */ + sector = bio->bi_iter.bi_sector; + recs[first_rdev_idx].bi_sector = sector + first_zone->dev_start; + recs[first_rdev_idx].sectors = sectors; + BUG_ON(recs[first_rdev_idx].rdev != first_rdev); + + /* recs[first_rdev_idx] is initialized with 'sectors', we need to + * handle the rested sectors, which is sotred in 'sectors' too. + */ + sectors = bio_sectors(bio) - sectors; + + /* bio may not be chunk size aligned, the split bio on first rdev + * may not be chunk size aligned too. But the rested split bios + * on rested rdevs must be chunk size aligned, and aligned to + * round down chunk number. + */ + zone_end = first_zone->zone_end; + rdev_idx = first_rdev_idx + 1; + sector = likely(is_power_of_2(chunk_sects)) + ? sector & (~(chunk_sects - 1)) + : chunk_sects * (sector/chunk_sects); + + while (rdev_idx < first_zone->nb_dev) { + recs[rdev_idx].bi_sector = sector + first_zone->dev_start; + if (sectors <= chunk_sects) { + recs[rdev_idx].sectors = sectors; + goto issue; + } + recs[rdev_idx].sectors = chunk_sects; + sectors -= chunk_sects; + rdev_idx++; + } + + sector += chunk_sects; + zone_start = sector + first_zone->dev_start; + if (zone_start == zone_end) { + zone_idx++; + zone_start = conf->strip_zone[zone_idx].dev_start; + } + + while (zone_idx < nr_strip_zones) { + int rdevs_in_zone = conf->strip_zone[zone_idx].nb_dev; + int chunks_per_rdev, rested_chunks, rested_sectors; + sector_t zone_sectors, grow_sectors; + int add_rested_sectors = 0; + + zone_end = conf->strip_zone[zone_idx].zone_end; + zone_sectors = zone_end - zone_start; + chunks_per_rdev = sectors; + rested_sectors = + sector_div(chunks_per_rdev, chunk_sects * rdevs_in_zone); + rested_chunks = rested_sectors; + rested_sectors = sector_div(rested_chunks, chunk_sects); + + if ((chunks_per_rdev * chunk_sects) > zone_sectors) + chunks_per_rdev = zone_sectors/chunk_sects; + + /* rested_chunks and rested_sectors go into next zone, we won't + * handle them in this zone. Set them to 0. + */ + if ((chunks_per_rdev * chunk_sects) == zone_sectors && + (rested_chunks != 0 || rested_sectors != 0)) { + if (rested_chunks != 0) + rested_chunks = 0; + if (rested_sectors != 0) + rested_sectors = 0; + } + + if (rested_chunks == 0 && rested_sectors != 0) + add_rested_sectors = 1; + + for (rdev_idx = 0; rdev_idx < rdevs_in_zone; rdev_idx++) { + /* if .sectors is not initailized (== 0), it indicates + * .bi_sector is not initialized neither. We initiate + * .bi_sector firstly, then set .sectors by + * grow_sectors. + */ + if (recs[rdev_idx].sectors == 0) + recs[rdev_idx].bi_sector = zone_start; + grow_sectors = chunks_per_rdev * chunk_sects; + if (rested_chunks) { + grow_sectors += chunk_sects; + rested_chunks--; + if (rested_chunks == 0 && + rested_sectors != 0) { + recs[rdev_idx].sectors += grow_sectors; + sectors -= grow_sectors; + add_rested_sectors = 1; + continue; + } + } + + /* if add_rested_sectors != 0, it indicates + * rested_sectors != 0 + */ + if (add_rested_sectors) + grow_sectors += rested_sectors; + recs[rdev_idx].sectors += grow_sectors; + sectors -= grow_sectors; + if (add_rested_sectors) + break; + } + + if (sectors == 0) + break; + zone_start = zone_end; + zone_idx++; + BUG_ON(zone_start != conf->strip_zone[zone_idx].dev_start); + } + + +issue: + /* recs contains the re-ordered requests layout, now we can + * chain split bios from recs + */ + for (rdev_idx = 0; rdev_idx < disks; rdev_idx++) { + if (rdev_idx == first_rdev_idx || + recs[rdev_idx].sectors == 0) + continue; + split = bio_split(bio, + recs[rdev_idx].sectors, + GFP_NOIO, + fs_bio_set); + if (split == NULL) + break; + bio_chain(split, bio); + BUG_ON(split->bi_iter.bi_size != recs[rdev_idx].sectors << 9); + split->bi_bdev = recs[rdev_idx].rdev->bdev; + split->bi_iter.bi_sector = recs[rdev_idx].bi_sector + + recs[rdev_idx].rdev->data_offset; + + if (unlikely(!blk_queue_discard( + bdev_get_queue(split->bi_bdev)))) + /* Just ignore it */ + bio_endio(split); + else + generic_make_request(split); + } + BUG_ON(bio->bi_iter.bi_size != recs[first_rdev_idx].sectors << 9); + bio->bi_iter.bi_sector = recs[first_rdev_idx].bi_sector + + recs[first_rdev_idx].rdev->data_offset; + bio->bi_bdev = recs[first_rdev_idx].rdev->bdev; + + if (unlikely(!blk_queue_discard(bdev_get_queue(bio->bi_bdev)))) + /* Just ignore it */ + bio_endio(bio); + else + generic_make_request(bio); + + kfree(recs); +} + static void raid0_make_request(struct mddev *mddev, struct bio *bio) { struct strip_zone *zone; @@ -463,6 +682,11 @@ static void raid0_make_request(struct mddev *mddev, struct bio *bio) return; } + if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) { + handle_discard_request(mddev, bio); + return; + } + do { sector_t sector = bio->bi_iter.bi_sector; unsigned chunk_sects = mddev->chunk_sectors; ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-07 11:50 ` raid0 vs. mkfs Coly Li @ 2016-12-07 12:03 ` Coly Li 2016-12-07 16:59 ` Shaohua Li 1 sibling, 0 replies; 10+ messages in thread From: Coly Li @ 2016-12-07 12:03 UTC (permalink / raw) To: Avi Kivity, NeilBrown; +Cc: linux-raid, linux-block On 2016/12/7 下午7:50, Coly Li wrote: > On 2016/11/30 上午6:45, Avi Kivity wrote: >> On 11/29/2016 11:14 PM, NeilBrown wrote: > [snip] > >>>> So I disagree that all the work should be pushed to the merging layer. >>>> It has less information to work with, so the fewer decisions it has to >>>> make, the better. >>> I think that the merging layer should be as efficient as it reasonably >>> can be, and particularly should take into account plugging. This >>> benefits all callers. >> >> Yes, but plugging does not mean "please merge anything you can until the >> unplug". >> >>> If it can be demonstrated that changes to some of the upper layers bring >>> further improvements with acceptable costs, then certainly it is good to >>> have those too. >> >> Generating millions of requests only to merge them again is >> inefficient. It happens in an edge case (TRIM of the entirety of a very >> large RAID), but it already caused on user to believe the system >> failed. I think the system should be more robust than that. > > Neil, > > As my understand, if a large discard bio received by > raid0_make_request(), for example it requests to discard chunk 1 to 24 > on a raid0 device built by 4 SSDs. This large discard bio will be split > and written to each SSD as the following layout, > > SSD1: C1,C5,C9,C13,C17,C21 > SSD2: C2,C6,C10,C14,C18,C22 > SSD3: C3,C7,C11,C15,C19,C23 > SSD4: C4,C8,C12,C16,C20,C24 > > Current raid0 code will call generic_make_request() for 24 times for > each split bio. But it is possible to calculate the final layout of each > split bio, so we can combine all the bios into four per-SSD large bio, > like this, > > bio1 (on SSD1): C{1,5,9,13,17,21} > bio2 (on SSD2): C{2,6,10,14,18,22} > bio3 (on SSD3): C{3,7,11,15,19,23} > bio4 (on SSD4): C{4,8,12,16,20,24} > > Now we only need to call generic_make_request() for 4 times. Rebuild the > per-device discard bios is more efficient in raid0 code then in block > layer. There are some reasons that I know, > - there are splice timeout, block layer cannot merge all split bio into > one large bio before time out. > - rebuilt per-device bio in raid0 is just by a few calculation, block > layer does merge on queue with list operations, it is slower. > - raid0 code knows its on disk layout, so rebuild per-device bio is > possible here. block layer has no idea on raid0 layout, it can only do > request merge. > > Avi, > > I compose a prototype patch, the code is not simple, indeed it is quite > complicated IMHO. > > I do a little research, some NVMe SSDs support whole device size > DISCARD, also I observe mkfs.xfs sends out a raid0 device size DISCARD > bio to block layer. But raid0_make_request() only receives 512KB size > DISCARD bio, block/blk-lib.c:__blkdev_issue_discard() splits the > original large bio into 512KB small bios, the limitation is from > q->limits.discard_granularity. > > At this moment, I don't know why a q->limits.discard_granularity is > 512KB even the underlying SSD supports whole device size discard. We > also need to fix q->limits.discard_granularity, otherwise > block/blk-lib.c:__blkdev_issue_discard() still does an inefficient loop > to split the original large discard bio into smaller ones and sends them > to raid0 code by next_bio(). > > I also CC this email to linux-block@vger.kernel.org to ask for help. My > question is, if a NVMe SSD supports whole-device-size DISCARD, is > q->limits.discard_granularity still necessary ? > > Here I also attach my prototype patch as a proof of concept, it is > runnable with Linux 4.9-rc7. Aha! It is not limits.discard_granularity, it is limits.max_discard_sectors. Which is set in raid0.c:raid0_run() by blk_queue_max_discard_sectors(). Here limits.maxZ_discard_sectors is set to raid0 chunk size. Interesting .... And, linux-block@vger.kernel.org, please ignore my noise. Coly ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-07 11:50 ` raid0 vs. mkfs Coly Li 2016-12-07 12:03 ` Coly Li @ 2016-12-07 16:59 ` Shaohua Li 2016-12-08 16:44 ` Coly Li 1 sibling, 1 reply; 10+ messages in thread From: Shaohua Li @ 2016-12-07 16:59 UTC (permalink / raw) To: Coly Li; +Cc: Avi Kivity, NeilBrown, linux-raid, linux-block On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: > On 2016/11/30 上午6:45, Avi Kivity wrote: > > On 11/29/2016 11:14 PM, NeilBrown wrote: > [snip] > > >>> So I disagree that all the work should be pushed to the merging layer. > >>> It has less information to work with, so the fewer decisions it has to > >>> make, the better. > >> I think that the merging layer should be as efficient as it reasonably > >> can be, and particularly should take into account plugging. This > >> benefits all callers. > > > > Yes, but plugging does not mean "please merge anything you can until the > > unplug". > > > >> If it can be demonstrated that changes to some of the upper layers bring > >> further improvements with acceptable costs, then certainly it is good to > >> have those too. > > > > Generating millions of requests only to merge them again is > > inefficient. It happens in an edge case (TRIM of the entirety of a very > > large RAID), but it already caused on user to believe the system > > failed. I think the system should be more robust than that. > > Neil, > > As my understand, if a large discard bio received by > raid0_make_request(), for example it requests to discard chunk 1 to 24 > on a raid0 device built by 4 SSDs. This large discard bio will be split > and written to each SSD as the following layout, > > SSD1: C1,C5,C9,C13,C17,C21 > SSD2: C2,C6,C10,C14,C18,C22 > SSD3: C3,C7,C11,C15,C19,C23 > SSD4: C4,C8,C12,C16,C20,C24 > > Current raid0 code will call generic_make_request() for 24 times for > each split bio. But it is possible to calculate the final layout of each > split bio, so we can combine all the bios into four per-SSD large bio, > like this, > > bio1 (on SSD1): C{1,5,9,13,17,21} > bio2 (on SSD2): C{2,6,10,14,18,22} > bio3 (on SSD3): C{3,7,11,15,19,23} > bio4 (on SSD4): C{4,8,12,16,20,24} > > Now we only need to call generic_make_request() for 4 times. Rebuild the > per-device discard bios is more efficient in raid0 code then in block > layer. There are some reasons that I know, > - there are splice timeout, block layer cannot merge all split bio into > one large bio before time out. > - rebuilt per-device bio in raid0 is just by a few calculation, block > layer does merge on queue with list operations, it is slower. > - raid0 code knows its on disk layout, so rebuild per-device bio is > possible here. block layer has no idea on raid0 layout, it can only do > request merge. Thanks for doing this, Coly! For raid0, this totally makes sense. The raid0 zones make things a little complicated though. I just had a brief look of your proposed patch, which looks really complicated. I'd suggest something like this: 1. split the bio according to zone boundary. 2. handle the splitted bio. since the bio is within zone range, calculating the start and end sector for each rdev should be easy. This will create slightly more bio to each rdev (not too many, since there aren't too many zones in practice) and block layer should easily merge these bios without much overhead. The benefit is a much simpler implementation. > I compose a prototype patch, the code is not simple, indeed it is quite > complicated IMHO. > > I do a little research, some NVMe SSDs support whole device size > DISCARD, also I observe mkfs.xfs sends out a raid0 device size DISCARD > bio to block layer. But raid0_make_request() only receives 512KB size > DISCARD bio, block/blk-lib.c:__blkdev_issue_discard() splits the > original large bio into 512KB small bios, the limitation is from > q->limits.discard_granularity. please adjust the max discard sectors for the queue. The original setting is chunk size. Thanks, Shaohua ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-07 16:59 ` Shaohua Li @ 2016-12-08 16:44 ` Coly Li 2016-12-08 19:19 ` Shaohua Li 2017-06-29 15:15 ` Avi Kivity 0 siblings, 2 replies; 10+ messages in thread From: Coly Li @ 2016-12-08 16:44 UTC (permalink / raw) To: Shaohua Li; +Cc: Avi Kivity, NeilBrown, linux-raid, linux-block [-- Attachment #1: Type: text/plain, Size: 2545 bytes --] On 2016/12/8 上午12:59, Shaohua Li wrote: > On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: [snip] > Thanks for doing this, Coly! For raid0, this totally makes sense. The raid0 > zones make things a little complicated though. I just had a brief look of your > proposed patch, which looks really complicated. I'd suggest something like > this: > 1. split the bio according to zone boundary. > 2. handle the splitted bio. since the bio is within zone range, calculating > the start and end sector for each rdev should be easy. > Hi Shaohua, Thanks for your suggestion! I try to modify the code by your suggestion, it is even more hard to make the code that way ... Because even split bios for each zone, all the corner cases still exist and should be taken care in every zoon. The code will be more complicated. > This will create slightly more bio to each rdev (not too many, since there > aren't too many zones in practice) and block layer should easily merge these > bios without much overhead. The benefit is a much simpler implementation. > >> I compose a prototype patch, the code is not simple, indeed it is quite >> complicated IMHO. >> >> I do a little research, some NVMe SSDs support whole device size >> DISCARD, also I observe mkfs.xfs sends out a raid0 device size DISCARD >> bio to block layer. But raid0_make_request() only receives 512KB size >> DISCARD bio, block/blk-lib.c:__blkdev_issue_discard() splits the >> original large bio into 512KB small bios, the limitation is from >> q->limits.discard_granularity. > > please adjust the max discard sectors for the queue. The original setting is > chunk size. This is a powerful suggestion, I change the max_discard_sectors to raid0 size, and fix some bugs, now the patch looks working well. The performance number is not bad. On 4x3TB NVMe raid0, format it with mkfs.xfs. Current upstream kernel spends 306 seconds, the patched kernel spends 15 seconds. I see average request size increases from 1 chunk (1024 sectors) to 2048 chunks (2097152 sectors). I don't know why the bios are still be split before raid0_make_request() receives them, after I set q->limits.max_discard_sectors to mddev->array_sectors. Can anybody give me a hint ? Here I attach the RFC v2 patch, if anybody wants to try it, please do it and response the result :-) I will take time to write a very detailed commit log and code comments to make this patch more easier to be understood. Ugly code, that's what I have to pay to gain better performance .... Thanks in advance. Coly [-- Attachment #2: raid0_handle_large_discard_bio.patch --] [-- Type: text/plain, Size: 11145 bytes --] Subject: [RFC v2] optimization for large size DISCARD bio by per-device bios This is a very early prototype, still needs more block layer code modification to make it work. Current upstream raid0_make_request() only handles TRIM/DISCARD bio by chunk size, it meams for large raid0 device built by SSDs will call million times generic_make_request() for the split bio. This patch tries to combine small bios into large one if they are on same real device and continuous on this real device, then send the combined large bio to underlying device by single call to generic_make_request(). For example, use mkfs.xfs to trim a raid0 device built with 4 x 3TB NVMeSSD, current upstream raid0_make_request() will call generic_make_request() 5.7 million times, with this patch only 4 calls to generic_make_request() is required. This patch won't work in real world, because in block/blk-lib.c: __blkdev_issue_discard() the original large bio will be split into smaller ones by restriction of discard_granularity. If some day SSD supports whole device sized discard_granularity, it will be very interesting then... The basic idea is, if a large discard bio received by raid0_make_request(), for example it requests to discard chunk 1 to 24 on a raid0 device built by 4 SSDs. This large discard bio will be split and written to each SSD as the following layout, SSD1: C1,C5,C9,C13,C17,C21 SSD2: C2,C6,C10,C14,C18,C22 SSD3: C3,C7,C11,C15,C19,C23 SSD4: C4,C8,C12,C16,C20,C24 Current raid0 code will call generic_make_request() for 24 times for each split bio. But it is possible to calculate the final layout of each split bio, so we can combine all the bios into four per-SSD large bio, like this, bio1 (on SSD1): C{1,5,9,13,17,21} bio2 (on SSD2): C{2,6,10,14,18,22} bio3 (on SSD3): C{3,7,11,15,19,23} bio4 (on SSD4): C{4,8,12,16,20,24} Now we only need to call generic_make_request() for 4 times. The code is not simple, I need more time to write text to complain how it works. Currently you can treat it as a proof of concept. Changelogs v1, Initial prototype. v2, Major changes inlcude, - rename function names, now handle_discard_bio() takes care in chunk size DISCARD bio and single disk sutiation, large DISCARD bio will be handled in handle_large_discard_bio(). - Set max_discard_sectors to raid0 device size. - Fix several bugs which I find in basic testing.. Signed-off-by: Coly Li <colyli@suse.de> --- drivers/md/raid0.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 1 deletion(-) diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 258986a..c7afe0c 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -378,7 +378,7 @@ static int raid0_run(struct mddev *mddev) blk_queue_max_hw_sectors(mddev->queue, mddev->chunk_sectors); blk_queue_max_write_same_sectors(mddev->queue, mddev->chunk_sectors); - blk_queue_max_discard_sectors(mddev->queue, mddev->chunk_sectors); + blk_queue_max_discard_sectors(mddev->queue, raid0_size(mddev, 0, 0)); blk_queue_io_min(mddev->queue, mddev->chunk_sectors << 9); blk_queue_io_opt(mddev->queue, @@ -452,6 +452,266 @@ static inline int is_io_in_chunk_boundary(struct mddev *mddev, } } + +struct bio_record { + sector_t bi_sector; + unsigned long sectors; + struct md_rdev *rdev; +}; + +static void handle_large_discard_bio(struct mddev *mddev, struct bio *bio) +{ + struct bio_record *recs = NULL; + struct bio *split; + struct r0conf *conf = mddev->private; + sector_t sectors, sector; + struct strip_zone *first_zone; + int zone_idx; + sector_t zone_start, zone_end; + int nr_strip_zones = conf->nr_strip_zones; + int disks; + int first_rdev_idx = -1, rdev_idx; + struct md_rdev *first_rdev; + unsigned int chunk_sects = mddev->chunk_sectors; + + sector = bio->bi_iter.bi_sector; + first_zone = find_zone(conf, §or); + first_rdev = map_sector(mddev, first_zone, sector, §or); + + /* bio is large enough to be split, allocate recs firstly */ + disks = mddev->raid_disks; + recs = kcalloc(disks, sizeof(struct bio_record), GFP_NOIO); + if (recs == NULL) { + printk(KERN_ERR "md/raid0:%s: failed to allocate memory " \ + "for bio_record", mdname(mddev)); + bio->bi_error = -ENOMEM; + bio_endio(bio); + return; + } + + zone_idx = first_zone - conf->strip_zone; + for (rdev_idx = 0; rdev_idx < first_zone->nb_dev; rdev_idx++) { + struct md_rdev *rdev; + + rdev = conf->devlist[zone_idx * disks + rdev_idx]; + recs[rdev_idx].rdev = rdev; + if (rdev == first_rdev) + first_rdev_idx = rdev_idx; + } + + sectors = chunk_sects - + (likely(is_power_of_2(chunk_sects)) + ? (sector & (chunk_sects - 1)) + : sector_div(sector, chunk_sects)); + sector = bio->bi_iter.bi_sector; + + recs[first_rdev_idx].bi_sector = sector + first_zone->dev_start; + recs[first_rdev_idx].sectors = sectors; + + /* recs[first_rdev_idx] is initialized with 'sectors', we need to + * handle the rested sectors, which is sotred in 'sectors' too. + */ + sectors = bio_sectors(bio) - sectors; + + /* bio may not be chunk size aligned, the split bio on first rdev + * may not be chunk size aligned too. But the rested split bios + * on rested rdevs must be chunk size aligned, and aligned to + * round down chunk number. + */ + zone_end = first_zone->zone_end; + rdev_idx = first_rdev_idx + 1; + sector = likely(is_power_of_2(chunk_sects)) + ? sector & (~(chunk_sects - 1)) + : chunk_sects * (sector/chunk_sects); + + while (rdev_idx < first_zone->nb_dev) { + if (recs[rdev_idx].sectors == 0) { + recs[rdev_idx].bi_sector = sector + first_zone->dev_start; + if (sectors <= chunk_sects) { + recs[rdev_idx].sectors = sectors; + goto issue; + } + recs[rdev_idx].sectors = chunk_sects; + sectors -= chunk_sects; + } + rdev_idx++; + } + + sector += chunk_sects; + zone_start = sector + first_zone->dev_start; + if (zone_start == zone_end) { + zone_idx++; + if (zone_idx == nr_strip_zones) { + if (sectors != 0) + printk(KERN_INFO "bio size exceeds raid0 " \ + "capability, ignore extra " \ + "TRIM/DISCARD range.\n"); + goto issue; + } + zone_start = conf->strip_zone[zone_idx].dev_start; + } + + while (zone_idx < nr_strip_zones) { + int rdevs_in_zone = conf->strip_zone[zone_idx].nb_dev; + int chunks_per_rdev, rested_chunks, rested_sectors; + sector_t zone_sectors, grow_sectors; + int add_rested_sectors = 0; + + zone_end = conf->strip_zone[zone_idx].zone_end; + zone_sectors = zone_end - zone_start; + chunks_per_rdev = sectors; + rested_sectors = + sector_div(chunks_per_rdev, chunk_sects * rdevs_in_zone); + rested_chunks = rested_sectors; + rested_sectors = sector_div(rested_chunks, chunk_sects); + + if ((chunks_per_rdev * chunk_sects) > zone_sectors) + chunks_per_rdev = zone_sectors/chunk_sects; + + /* rested_chunks and rested_sectors go into next zone, we won't + * handle them in this zone. Set them to 0. + */ + if ((chunks_per_rdev * chunk_sects) == zone_sectors && + (rested_chunks != 0 || rested_sectors != 0)) { + if (rested_chunks != 0) + rested_chunks = 0; + if (rested_sectors != 0) + rested_sectors = 0; + } + + if (rested_chunks == 0 && rested_sectors != 0) + add_rested_sectors ++; + + for (rdev_idx = 0; rdev_idx < rdevs_in_zone; rdev_idx++) { + /* if .sectors is not initailized (== 0), it indicates + * .bi_sector is not initialized neither. We initiate + * .bi_sector firstly, then set .sectors by + * grow_sectors. + */ + if (recs[rdev_idx].sectors == 0) + recs[rdev_idx].bi_sector = zone_start; + grow_sectors = chunks_per_rdev * chunk_sects; + if (rested_chunks) { + grow_sectors += chunk_sects; + rested_chunks--; + if (rested_chunks == 0 && + rested_sectors != 0) { + recs[rdev_idx].sectors += grow_sectors; + sectors -= grow_sectors; + add_rested_sectors ++; + continue; + } + } + + /* if add_rested_sectors != 0, it indicates + * rested_sectors != 0 + */ + if (add_rested_sectors == 1) { + grow_sectors += rested_sectors; + add_rested_sectors ++; + } + recs[rdev_idx].sectors += grow_sectors; + sectors -= grow_sectors; + if (sectors == 0) + break; + } + + if (sectors == 0) + break; + zone_start = zone_end; + zone_idx++; + if (zone_idx < nr_strip_zones) + BUG_ON(zone_start != conf->strip_zone[zone_idx].dev_start); + } + + +issue: + /* recs contains the re-ordered requests layout, now we can + * chain split bios from recs + */ + for (rdev_idx = 0; rdev_idx < disks; rdev_idx++) { + if (rdev_idx == first_rdev_idx || + recs[rdev_idx].sectors == 0) + continue; + split = bio_split(bio, + recs[rdev_idx].sectors, + GFP_NOIO, + fs_bio_set); + if (split == NULL) + break; + bio_chain(split, bio); + BUG_ON(split->bi_iter.bi_size != recs[rdev_idx].sectors << 9); + split->bi_bdev = recs[rdev_idx].rdev->bdev; + split->bi_iter.bi_sector = recs[rdev_idx].bi_sector + + recs[rdev_idx].rdev->data_offset; + + if (unlikely(!blk_queue_discard( + bdev_get_queue(split->bi_bdev)))) + /* Just ignore it */ + bio_endio(split); + else + generic_make_request(split); + } + BUG_ON(bio->bi_iter.bi_size != recs[first_rdev_idx].sectors << 9); + bio->bi_iter.bi_sector = recs[first_rdev_idx].bi_sector + + recs[first_rdev_idx].rdev->data_offset; + bio->bi_bdev = recs[first_rdev_idx].rdev->bdev; + + if (unlikely(!blk_queue_discard(bdev_get_queue(bio->bi_bdev)))) + /* Just ignore it */ + bio_endio(bio); + else + generic_make_request(bio); + + kfree(recs); +} + +static void handle_discard_bio(struct mddev *mddev, struct bio *bio) +{ + struct r0conf *conf = mddev->private; + unsigned int chunk_sects = mddev->chunk_sectors; + sector_t sector, sectors; + struct md_rdev *rdev; + struct strip_zone *zone; + + sector = bio->bi_iter.bi_sector; + zone = find_zone(conf, §or); + rdev = map_sector(mddev, zone, sector, §or); + bio->bi_bdev = rdev->bdev; + sectors = chunk_sects - + (likely(is_power_of_2(chunk_sects)) + ? (sector & (chunk_sects - 1)) + : sector_div(sector, chunk_sects)); + + if (unlikely(sectors >= bio_sectors(bio))) { + bio->bi_iter.bi_sector = sector + zone->dev_start + + rdev->data_offset; + goto single_bio; + } + + if (unlikely(zone->nb_dev == 1)) { + sectors = conf->strip_zone[0].zone_end - + sector; + if (bio_sectors(bio) > sectors) + bio->bi_iter.bi_size = sectors << 9; + bio->bi_iter.bi_sector = sector + rdev->data_offset; + goto single_bio; + } + + handle_large_discard_bio(mddev, bio); + return; + +single_bio: + if (unlikely(!blk_queue_discard(bdev_get_queue(bio->bi_bdev)))) + /* Just ignore it */ + bio_endio(bio); + else + generic_make_request(bio); + + return; +} + + static void raid0_make_request(struct mddev *mddev, struct bio *bio) { struct strip_zone *zone; @@ -463,6 +723,11 @@ static void raid0_make_request(struct mddev *mddev, struct bio *bio) return; } + if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) { + handle_discard_bio(mddev, bio); + return; + } + do { sector_t sector = bio->bi_iter.bi_sector; unsigned chunk_sects = mddev->chunk_sectors; ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-08 16:44 ` Coly Li @ 2016-12-08 19:19 ` Shaohua Li 2016-12-09 7:34 ` Coly Li 2017-06-29 15:15 ` Avi Kivity 1 sibling, 1 reply; 10+ messages in thread From: Shaohua Li @ 2016-12-08 19:19 UTC (permalink / raw) To: Coly Li; +Cc: Avi Kivity, NeilBrown, linux-raid, linux-block On Fri, Dec 09, 2016 at 12:44:57AM +0800, Coly Li wrote: > On 2016/12/8 上午12:59, Shaohua Li wrote: > > On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: > [snip] > > Thanks for doing this, Coly! For raid0, this totally makes sense. The raid0 > > zones make things a little complicated though. I just had a brief look of your > > proposed patch, which looks really complicated. I'd suggest something like > > this: > > 1. split the bio according to zone boundary. > > 2. handle the splitted bio. since the bio is within zone range, calculating > > the start and end sector for each rdev should be easy. > > > > Hi Shaohua, > > Thanks for your suggestion! I try to modify the code by your suggestion, > it is even more hard to make the code that way ... > > Because even split bios for each zone, all the corner cases still exist > and should be taken care in every zoon. The code will be more complicated. Not sure why it makes the code more complicated. Probably I'm wrong, but Just want to make sure we are in the same page: split the bio according to zone boundary, then handle the splitted bio separately. Calculating end/start point of each rdev for the new bio within a zone should be simple. we then clone a bio for each rdev and dispatch. So for example: Disk 0: D0 D2 D4 D6 D7 Disk 1: D1 D3 D5 zone 0 is from D0 - D5, zone 1 is from D6 - D7 If bio is from D1 to D7, we split it to 2 bios, one is D1 - D5, the other D6 - D7. For D1 - D5, we dispatch 2 bios. D1 - D5 for disk 1, D2 - D4 for disk 0 For D6 - D7, we just dispatch to disk 0. What kind of corner case makes this more complicated? > > This will create slightly more bio to each rdev (not too many, since there > > aren't too many zones in practice) and block layer should easily merge these > > bios without much overhead. The benefit is a much simpler implementation. > > > >> I compose a prototype patch, the code is not simple, indeed it is quite > >> complicated IMHO. > >> > >> I do a little research, some NVMe SSDs support whole device size > >> DISCARD, also I observe mkfs.xfs sends out a raid0 device size DISCARD > >> bio to block layer. But raid0_make_request() only receives 512KB size > >> DISCARD bio, block/blk-lib.c:__blkdev_issue_discard() splits the > >> original large bio into 512KB small bios, the limitation is from > >> q->limits.discard_granularity. > > > > please adjust the max discard sectors for the queue. The original setting is > > chunk size. > > This is a powerful suggestion, I change the max_discard_sectors to raid0 > size, and fix some bugs, now the patch looks working well. The > performance number is not bad. > > On 4x3TB NVMe raid0, format it with mkfs.xfs. Current upstream kernel > spends 306 seconds, the patched kernel spends 15 seconds. I see average > request size increases from 1 chunk (1024 sectors) to 2048 chunks > (2097152 sectors). > > I don't know why the bios are still be split before raid0_make_request() > receives them, after I set q->limits.max_discard_sectors to > mddev->array_sectors. Can anybody give me a hint ? That probably is from disk_stack_limits. try set the max_discard_sectors after it. > Here I attach the RFC v2 patch, if anybody wants to try it, please do it > and response the result :-) > > I will take time to write a very detailed commit log and code comments > to make this patch more easier to be understood. Ugly code, that's what > I have to pay to gain better performance .... Can't say I like it :). Hard to read and the memory allocation is ugly. Please check if there is simpler solution first before writting detailed commit log. Thanks, Shaohua ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-08 19:19 ` Shaohua Li @ 2016-12-09 7:34 ` Coly Li 2016-12-12 3:17 ` NeilBrown 0 siblings, 1 reply; 10+ messages in thread From: Coly Li @ 2016-12-09 7:34 UTC (permalink / raw) To: Shaohua Li; +Cc: Avi Kivity, NeilBrown, linux-raid, linux-block On 2016/12/9 上午3:19, Shaohua Li wrote: > On Fri, Dec 09, 2016 at 12:44:57AM +0800, Coly Li wrote: >> On 2016/12/8 上午12:59, Shaohua Li wrote: >>> On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: >> [snip] >>> Thanks for doing this, Coly! For raid0, this totally makes sense. The raid0 >>> zones make things a little complicated though. I just had a brief look of your >>> proposed patch, which looks really complicated. I'd suggest something like >>> this: >>> 1. split the bio according to zone boundary. >>> 2. handle the splitted bio. since the bio is within zone range, calculating >>> the start and end sector for each rdev should be easy. >>> >> >> Hi Shaohua, >> >> Thanks for your suggestion! I try to modify the code by your suggestion, >> it is even more hard to make the code that way ... >> >> Because even split bios for each zone, all the corner cases still exist >> and should be taken care in every zoon. The code will be more complicated. > > Not sure why it makes the code more complicated. Probably I'm wrong, but Just > want to make sure we are in the same page: split the bio according to zone > boundary, then handle the splitted bio separately. Calculating end/start point > of each rdev for the new bio within a zone should be simple. we then clone a > bio for each rdev and dispatch. So for example: > Disk 0: D0 D2 D4 D6 D7 > Disk 1: D1 D3 D5 > zone 0 is from D0 - D5, zone 1 is from D6 - D7 > If bio is from D1 to D7, we split it to 2 bios, one is D1 - D5, the other D6 - D7. > For D1 - D5, we dispatch 2 bios. D1 - D5 for disk 1, D2 - D4 for disk 0 > For D6 - D7, we just dispatch to disk 0. > What kind of corner case makes this more complicated? > Let me explain the corner cases. When upper layer code issues a DISCARD bio, the bio->bi_iter.bi_sector may not be chunk size aligned, and bio->bi_iter.bi_size may not be (chunk_sects*nb_dev) sectors aligned. In raid0, we can't simply round up/down them into chunk size aligned number, otherwise data lost/corruption will happen. Therefore for each DISCARD bio that raid0_make_request() receive, the beginning and ending parts of this bio should be treat very carefully. All the corner cases *come from here*, they are not about number of zones or rdevs, it is about whether bio->bi_iter.bi_sector and bio->bi_iter.bi_size are chunk size aligned or not. - beginning of the bio If bio->bi_iter.bi_sector is not chunk size aligned, current raid0 code will split the beginning part into split bio which only contains sectors from bio->bi_iter.sector to next chunk size aligned offset, and issue this bio by generic_make_request(). But in discard_large_discard_bio() we can't issue the split bio now, we have to record lenth of this split bio into a per-device structure, and issue a split bio after all the sectors of the DISCARD bio are calculated. So I use recs[first_rdev_idx].bi_sector to rcoard bi_iter.bi_sector of the split bio, recs[first_rdev_idx].sectors to record length of this split bio, and recs[first_rdev_idx].rdev to record address of the real device where the split bio will be sent to. After the first non-chunk-size aligned part handled, we need to look into the next rdev for next chunk of DISCARD bio. Now the sector offset is chunk size aligned, but there are still two condition: 1) If the first_rdev is not the last read device in current zone, then on the next real device, its per-device bio will start on a round down chunk offset of recs[first_rdev_idx].bi_sector. If recs[first_rdev_idx].bi_sector is chunk size aligned, the next real device's per-device bio will start at the same chunk offset. 2) If first_rdev is the last real device in current zone, then next rdev to handle is the number 0 real device among conf->strip_zone[zone_idx].nb_dev real devices. In this case, the bi_iter.bi_setor of the per-device bio of this real device, is the chunk offset next to chunk which recs[first_rdev_idx].bi_sector hits on recs[first_rdev_idx].rdev. - ending part of the bio If length of the DISCARD bio is not (chunk_sects * nb_dev) sectors aligned, after we handled one or more (chunk_sects*nb_dev)) aligned sectors, the rested sectors are less then chunk_sects*nb_dev, but these sectors may still fit in some rested_chunks and rested_sectors. We also need to handle them carefully. If rested_chunks is not 0, because chunks are linearly allocated on each real device in current zone, we need to add chunk_sects to recs[rdev_idx].sectors, where rdev_idx starts from 0 to strip_zone[zone_idx].nb_dev - 1. When we move to next real device (rdev_idx ++), we need to reduce one chunk from rested_chunk (rested_chunk --). If rested_chunk reaches 0, we start to handle rested_sectors. rested_sectors will be added to the next real device, we just simply add rested_sectors to recs[rdev_idx].sectors. There is a corner case that after the calculation of DISCARD bio, rested_chunks is 0, and rested_sectors is not 0. In this case, we only need to add rested_sectors to number 0 real device of the zone, which is recs[0]. A DISCARD bio is probably to only cover one raid0 zone, but all the above corner cases have to be taken care. Therefore submitting split bios for each zone, does not make the code simpler. To handle the details correctly is really boring, if I can explain to you face to face, that will be much easier. >>> This will create slightly more bio to each rdev (not too many, since there >>> aren't too many zones in practice) and block layer should easily merge these >>> bios without much overhead. The benefit is a much simpler implementation. >>> >>>> I compose a prototype patch, the code is not simple, indeed it is quite >>>> complicated IMHO. >>>> >>>> I do a little research, some NVMe SSDs support whole device size >>>> DISCARD, also I observe mkfs.xfs sends out a raid0 device size DISCARD >>>> bio to block layer. But raid0_make_request() only receives 512KB size >>>> DISCARD bio, block/blk-lib.c:__blkdev_issue_discard() splits the >>>> original large bio into 512KB small bios, the limitation is from >>>> q->limits.discard_granularity. >>> >>> please adjust the max discard sectors for the queue. The original setting is >>> chunk size. >> >> This is a powerful suggestion, I change the max_discard_sectors to raid0 >> size, and fix some bugs, now the patch looks working well. The >> performance number is not bad. >> >> On 4x3TB NVMe raid0, format it with mkfs.xfs. Current upstream kernel >> spends 306 seconds, the patched kernel spends 15 seconds. I see average >> request size increases from 1 chunk (1024 sectors) to 2048 chunks >> (2097152 sectors). >> >> I don't know why the bios are still be split before raid0_make_request() >> receives them, after I set q->limits.max_discard_sectors to >> mddev->array_sectors. Can anybody give me a hint ? > > That probably is from disk_stack_limits. try set the max_discard_sectors after it. > I know why the bio is still split, (1<<32)/512 = 8388608, in __blkdev_issue_discard(), req_sects is decided by: /* Make sure bi_size doesn't overflow */ req_sects = min_t(sector_t, nr_sects, UINT_MAX >> 9); I try to simply modify bi_size from unsigned int to unsigned long, and change the above limit to ULONG_MAX>>9, kernel panics. It seems change bi_sector from unsigned int to unsigned long is not simple. If we don't change bi_iter.bi_size to unsigned long, this is the best effort now. >> Here I attach the RFC v2 patch, if anybody wants to try it, please do it >> and response the result :-) >> >> I will take time to write a very detailed commit log and code comments >> to make this patch more easier to be understood. Ugly code, that's what >> I have to pay to gain better performance .... > > Can't say I like it :). Hard to read and the memory allocation is ugly. Please > check if there is simpler solution first before writting detailed commit log. I don't like it neither .... I can imagine how hard to read it, because even explain it in text is difficult... A lot of details to take care, and the calculation should be exactly match in the end. I tried to avoid to allocate memory for recs[], but without a data structure to record the per-devcie bio attribution, I cannot find a better way to write the code. There is only one thing makes me to be 1%+ comfortable is, which is bio_split() also allocates memory ^_^ Coly ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-09 7:34 ` Coly Li @ 2016-12-12 3:17 ` NeilBrown 0 siblings, 0 replies; 10+ messages in thread From: NeilBrown @ 2016-12-12 3:17 UTC (permalink / raw) To: Coly Li, Shaohua Li; +Cc: Avi Kivity, linux-raid, linux-block [-- Attachment #1: Type: text/plain, Size: 3524 bytes --] On Fri, Dec 09 2016, Coly Li wrote: > On 2016/12/9 上午3:19, Shaohua Li wrote: >> On Fri, Dec 09, 2016 at 12:44:57AM +0800, Coly Li wrote: >>> On 2016/12/8 上午12:59, Shaohua Li wrote: >>>> On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: >>> [snip] >>>> Thanks for doing this, Coly! For raid0, this totally makes sense. The raid0 >>>> zones make things a little complicated though. I just had a brief look of your >>>> proposed patch, which looks really complicated. I'd suggest something like >>>> this: >>>> 1. split the bio according to zone boundary. >>>> 2. handle the splitted bio. since the bio is within zone range, calculating >>>> the start and end sector for each rdev should be easy. >>>> >>> >>> Hi Shaohua, >>> >>> Thanks for your suggestion! I try to modify the code by your suggestion, >>> it is even more hard to make the code that way ... >>> >>> Because even split bios for each zone, all the corner cases still exist >>> and should be taken care in every zoon. The code will be more complicated. >> >> Not sure why it makes the code more complicated. Probably I'm wrong, but Just >> want to make sure we are in the same page: split the bio according to zone >> boundary, then handle the splitted bio separately. Calculating end/start point >> of each rdev for the new bio within a zone should be simple. we then clone a >> bio for each rdev and dispatch. So for example: >> Disk 0: D0 D2 D4 D6 D7 >> Disk 1: D1 D3 D5 >> zone 0 is from D0 - D5, zone 1 is from D6 - D7 >> If bio is from D1 to D7, we split it to 2 bios, one is D1 - D5, the other D6 - D7. >> For D1 - D5, we dispatch 2 bios. D1 - D5 for disk 1, D2 - D4 for disk 0 >> For D6 - D7, we just dispatch to disk 0. >> What kind of corner case makes this more complicated? >> > > Let me explain the corner cases. > > When upper layer code issues a DISCARD bio, the bio->bi_iter.bi_sector > may not be chunk size aligned, and bio->bi_iter.bi_size may not be > (chunk_sects*nb_dev) sectors aligned. In raid0, we can't simply round > up/down them into chunk size aligned number, otherwise data > lost/corruption will happen. > > Therefore for each DISCARD bio that raid0_make_request() receive, the > beginning and ending parts of this bio should be treat very carefully. > All the corner cases *come from here*, they are not about number of > zones or rdevs, it is about whether bio->bi_iter.bi_sector and > bio->bi_iter.bi_size are chunk size aligned or not. > > - beginning of the bio > If bio->bi_iter.bi_sector is not chunk size aligned, current raid0 > code will split the beginning part into split bio which only contains > sectors from bio->bi_iter.sector to next chunk size aligned offset, and > issue this bio by generic_make_request(). But in > discard_large_discard_bio() we can't issue the split bio now, we have to > record lenth of this split bio into a per-device structure, and issue a Why? Why cannot you just split of the start of the bio and chain it with the rest of the bio? If the bio doesn't start at the beginning of a stripe, just split of the first (partial) chunk exactly was we currently do. If it does start at the beginning of a stripe, then split off a whole number of stripes and allocate one bio for each device. Chain those to the original bio together with any remainder (which isn't a whole stripe). I think that if you make use of bio_split() and bio_chain() properly, the code will be much simpler. NeilBrown [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 832 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2016-12-08 16:44 ` Coly Li 2016-12-08 19:19 ` Shaohua Li @ 2017-06-29 15:15 ` Avi Kivity 2017-06-29 15:31 ` Coly Li 1 sibling, 1 reply; 10+ messages in thread From: Avi Kivity @ 2017-06-29 15:15 UTC (permalink / raw) To: Coly Li, Shaohua Li; +Cc: NeilBrown, linux-raid, linux-block On 12/08/2016 06:44 PM, Coly Li wrote: > On 2016/12/8 上午12:59, Shaohua Li wrote: >> On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: > [snip] >> Thanks for doing this, Coly! For raid0, this totally makes sense. The raid0 >> zones make things a little complicated though. I just had a brief look of your >> proposed patch, which looks really complicated. I'd suggest something like >> this: >> 1. split the bio according to zone boundary. >> 2. handle the splitted bio. since the bio is within zone range, calculating >> the start and end sector for each rdev should be easy. >> > Hi Shaohua, > > Thanks for your suggestion! I try to modify the code by your suggestion, > it is even more hard to make the code that way ... > > Because even split bios for each zone, all the corner cases still exist > and should be taken care in every zoon. The code will be more complicated. > Hi Coly, Did you manage to complete this patch? We are seeing its effect, not only with mkfs, but also with fstrim(8). Avi ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2017-06-29 15:15 ` Avi Kivity @ 2017-06-29 15:31 ` Coly Li 2017-06-29 15:36 ` Avi Kivity 0 siblings, 1 reply; 10+ messages in thread From: Coly Li @ 2017-06-29 15:31 UTC (permalink / raw) To: Avi Kivity, Shaohua Li; +Cc: NeilBrown, linux-raid, linux-block On 2017/6/29 下午11:15, Avi Kivity wrote: > > > On 12/08/2016 06:44 PM, Coly Li wrote: >> On 2016/12/8 上午12:59, Shaohua Li wrote: >>> On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: >> [snip] >>> Thanks for doing this, Coly! For raid0, this totally makes sense. The >>> raid0 >>> zones make things a little complicated though. I just had a brief >>> look of your >>> proposed patch, which looks really complicated. I'd suggest something >>> like >>> this: >>> 1. split the bio according to zone boundary. >>> 2. handle the splitted bio. since the bio is within zone range, >>> calculating >>> the start and end sector for each rdev should be easy. >>> >> Hi Shaohua, >> >> Thanks for your suggestion! I try to modify the code by your suggestion, >> it is even more hard to make the code that way ... >> >> Because even split bios for each zone, all the corner cases still exist >> and should be taken care in every zoon. The code will be more >> complicated. >> > > Hi Coly, > > Did you manage to complete this patch? We are seeing its effect, not > only with mkfs, but also with fstrim(8). Hi Avi, Shaohua makes another much better patch, which is merged into mainline kernel in v4.12-rc2. The commit is '29efc390b946 ("md/md0: optimize raid0 discard handling")'. Hope this is informative. Coly ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: raid0 vs. mkfs 2017-06-29 15:31 ` Coly Li @ 2017-06-29 15:36 ` Avi Kivity 0 siblings, 0 replies; 10+ messages in thread From: Avi Kivity @ 2017-06-29 15:36 UTC (permalink / raw) To: Coly Li, Shaohua Li; +Cc: NeilBrown, linux-raid, linux-block On 06/29/2017 06:31 PM, Coly Li wrote: > On 2017/6/29 下午11:15, Avi Kivity wrote: >> >> On 12/08/2016 06:44 PM, Coly Li wrote: >>> On 2016/12/8 上午12:59, Shaohua Li wrote: >>>> On Wed, Dec 07, 2016 at 07:50:33PM +0800, Coly Li wrote: >>> [snip] >>>> Thanks for doing this, Coly! For raid0, this totally makes sense. The >>>> raid0 >>>> zones make things a little complicated though. I just had a brief >>>> look of your >>>> proposed patch, which looks really complicated. I'd suggest something >>>> like >>>> this: >>>> 1. split the bio according to zone boundary. >>>> 2. handle the splitted bio. since the bio is within zone range, >>>> calculating >>>> the start and end sector for each rdev should be easy. >>>> >>> Hi Shaohua, >>> >>> Thanks for your suggestion! I try to modify the code by your suggestion, >>> it is even more hard to make the code that way ... >>> >>> Because even split bios for each zone, all the corner cases still exist >>> and should be taken care in every zoon. The code will be more >>> complicated. >>> >> Hi Coly, >> >> Did you manage to complete this patch? We are seeing its effect, not >> only with mkfs, but also with fstrim(8). > Hi Avi, > > Shaohua makes another much better patch, which is merged into mainline > kernel in v4.12-rc2. > > The commit is '29efc390b946 ("md/md0: optimize raid0 discard handling")'. > > Hope this is informative. > Thanks a lot, that's great news. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2017-06-29 15:36 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <56c83c4e-d451-07e5-88e2-40b085d8681c@scylladb.com>
[not found] ` <87oa108a1x.fsf@notabene.neil.brown.name>
[not found] ` <286a5fc1-eda3-0421-a88e-b03c09403259@scylladb.com>
[not found] ` <87inr880au.fsf@notabene.neil.brown.name>
[not found] ` <df73ebc4-9b78-09b5-022b-089c30dea17c@scylladb.com>
[not found] ` <87d1he7zv9.fsf@notabene.neil.brown.name>
[not found] ` <33bb250a-4dfd-0acc-9958-30fdac10918c@scylladb.com>
2016-12-07 11:50 ` raid0 vs. mkfs Coly Li
2016-12-07 12:03 ` Coly Li
2016-12-07 16:59 ` Shaohua Li
2016-12-08 16:44 ` Coly Li
2016-12-08 19:19 ` Shaohua Li
2016-12-09 7:34 ` Coly Li
2016-12-12 3:17 ` NeilBrown
2017-06-29 15:15 ` Avi Kivity
2017-06-29 15:31 ` Coly Li
2017-06-29 15:36 ` Avi Kivity
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox