Skip to contentSkip to conversation

MD: improve raid1/10 write performance for fast storage

PATCH Superseded Shaohua Li 2 participants 14 messages May 23, 2012 – May 24, 2012 MD RAID

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore

Shaohua Li

author
#0 lore raw

cover MD: improve raid1/10 write performance for fast storage

In raid1/10, all write requests are dispatched in a single thread. In fast storage, the thread is a bottleneck, because it dispatches request too slow. Also the thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue. Both these are not a big deal for slow storage.

Switching the dispatching to percpu/perthread based dramatically increases performance. The more raid disk number is, the more performance boosts. In a 4-disk raid10 setup, this can double the throughput.

percpu/perthread based dispatch doesn't harm slow storage. This is the way how raw device is accessed, and there is correct block plug set which can help do request merge and reduce lock contention.

author
#1 lore raw

PATCH 1/4 raid1: directly dispatch write request if no bitmap

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If no bitmap, there is no point to queue bio to a thread and dispatch it in the thread. Directly dispatching bio doesn't impact correctness and removes above bottleneck.

Multiple threads dispatch requests could potentially reduce request merge and increase lock contention. For slow stroage, we just worry about request merge. Caller of .make_request should already have correct block plug set, which will take care of request merge and locking just like accessing raw device, so we don't need worry about this too much.

In a 4k randwrite test with a 2 disks setup, below patch can provide 20% ~ 50% performance improvements depending on numa binding.

---
 drivers/md/raid1.c |   11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)
drivers/md/raid1.c +7 -4
Index: linux/drivers/md/raid1.c
===================================================================
@@ -1187,10 +1187,13 @@ read_again:
 		mbio->bi_private = r1_bio;
 
 		atomic_inc(&r1_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);
+		if (bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 	}
 	/* Mustn't call r1_bio_write_done before this next test,
 	 * as it could result in the bio being freed.
#2 lore raw

Re: [patch 1/4] raid1: directly dispatch write request if no bitmap

Quoted 36 lines · On Wed, 23 May 2012 15:26:20 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If no bitmap, there is no point to queue bio to a thread and dispatch it in the thread. Directly dispatching bio doesn't impact correctness and removes above bottleneck.

Multiple threads dispatch requests could potentially reduce request merge and increase lock contention. For slow stroage, we just worry about request merge. Caller of .make_request should already have correct block plug set, which will take care of request merge and locking just like accessing raw device, so we don't need worry about this too much.

In a 4k randwrite test with a 2 disks setup, below patch can provide 20% ~ 50% performance improvements depending on numa binding.

---
 drivers/md/raid1.c |   11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

Index: linux/drivers/md/raid1.c
===================================================================

drivers/md/raid1.c +0 -4
@@ -1187,10 +1187,13 @@ read_again:
 		mbio->bi_private = r1_bio;
 
 		atomic_inc(&r1_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);

drivers/md/raid1.c read_again: · line 1199

Quoted 10 lines
drivers/md/raid1.c +7 -0
+		if (bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 	}
 	/* Mustn't call r1_bio_write_done before this next test,
 	 * as it could result in the bio being freed.

This looks like it should be 'obviously correct' but unfortunately it isn't.

If this raid1 is beneath a dm device (e.g. LVM), then generic_make_request will queue the request internally and not actually start it.
In particular this means that the cloned bio has no chance of being released before the next clone_bio call which is made on the next time around the loop.

This can lead to a deadlock as the clone_bio() might be waiting for that first cloned bio to be released (if memory is really tight).

i.e. when allocating multiple bios from a mempool, we need to arrange for them to be submitted by a separate thread.

Thanks,
NeilBrown

author
#3 lore raw

Re: [patch 1/4] raid1: directly dispatch write request if no bitmap

Quoted 56 lines · NeilBrown
Quoted 36 lines · On Wed, 23 May 2012 15:26:20 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If no bitmap, there is no point to queue bio to a thread and dispatch it in the thread. Directly dispatching bio doesn't impact correctness and removes above bottleneck.

Multiple threads dispatch requests could potentially reduce request merge and increase lock contention. For slow stroage, we just worry about request merge. Caller of .make_request should already have correct block plug set, which will take care of request merge and locking just like accessing raw device, so we don't need worry about this too much.

In a 4k randwrite test with a 2 disks setup, below patch can provide 20% ~ 50% performance improvements depending on numa binding.

---
 drivers/md/raid1.c |   11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

Index: linux/drivers/md/raid1.c
===================================================================

drivers/md/raid1.c +0 -4
@@ -1187,10 +1187,13 @@ read_again:
 		mbio->bi_private = r1_bio;
 
 		atomic_inc(&r1_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);

drivers/md/raid1.c read_again: · line 1199

Quoted 10 lines
drivers/md/raid1.c +7 -0
+		if (bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 	}
 	/* Mustn't call r1_bio_write_done before this next test,
 	 * as it could result in the bio being freed.

This looks like it should be 'obviously correct' but unfortunately it isn't.

If this raid1 is beneath a dm device (e.g. LVM), then generic_make_request will queue the request internally and not actually start it.
In particular this means that the cloned bio has no chance of being released before the next clone_bio call which is made on the next time around the loop.

Quoted 5 lines

This can lead to a deadlock as the clone_bio() might be waiting for that first cloned bio to be released (if memory is really tight).

i.e. when allocating multiple bios from a mempool, we need to arrange for them to be submitted by a separate thread.

If there isn't block plug, generic_make_request will dispatch the request directly. If there is, when clone_bio() waits for memory, it will sleep/schedule, which will trigger block unplug and dispatch the first bio, so no deadlock to me. Am I misunderstanding you?

Thanks,
Shaohua

#4 lore raw

Re: [patch 1/4] raid1: directly dispatch write request if no bitmap

Quoted 57 lines · On Thu, 24 May 2012 10:54:25 +0800 Shaohua Li <[email protected]> wrote:
Quoted 56 lines · NeilBrown
Quoted 36 lines · On Wed, 23 May 2012 15:26:20 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If no bitmap, there is no point to queue bio to a thread and dispatch it in the thread. Directly dispatching bio doesn't impact correctness and removes above bottleneck.

Multiple threads dispatch requests could potentially reduce request merge and increase lock contention. For slow stroage, we just worry about request merge. Caller of .make_request should already have correct block plug set, which will take care of request merge and locking just like accessing raw device, so we don't need worry about this too much.

In a 4k randwrite test with a 2 disks setup, below patch can provide 20% ~ 50% performance improvements depending on numa binding.

---
 drivers/md/raid1.c |   11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

Index: linux/drivers/md/raid1.c
===================================================================

drivers/md/raid1.c +0 -4
@@ -1187,10 +1187,13 @@ read_again:
 		mbio->bi_private = r1_bio;
 
 		atomic_inc(&r1_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);

drivers/md/raid1.c read_again: · line 1199

Quoted 10 lines
drivers/md/raid1.c +7 -0
+		if (bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 	}
 	/* Mustn't call r1_bio_write_done before this next test,
 	 * as it could result in the bio being freed.

This looks like it should be 'obviously correct' but unfortunately it isn't.

If this raid1 is beneath a dm device (e.g. LVM), then generic_make_request will queue the request internally and not actually start it.
In particular this means that the cloned bio has no chance of being released before the next clone_bio call which is made on the next time around the loop.

Quoted 8 lines
Quoted 5 lines

This can lead to a deadlock as the clone_bio() might be waiting for that first cloned bio to be released (if memory is really tight).

i.e. when allocating multiple bios from a mempool, we need to arrange for them to be submitted by a separate thread.

If there isn't block plug, generic_make_request will dispatch the request directly.

Not necessarily. It might queue it:

	if (current->bio_list) {
		bio_list_add(current->bio_list, bio);
		return;
	}

NeilBrown

If there is, when clone_bio() waits for memory, it will

Quoted 5 lines

sleep/schedule, which will trigger block unplug and dispatch the first bio, so no deadlock to me. Am I misunderstanding you?

Thanks,
Shaohua

author
#5 lore raw

Re: [patch 1/4] raid1: directly dispatch write request if no bitmap

Quoted 65 lines · NeilBrown
Quoted 63 lines · On Thu, 24 May 2012 10:54:25 +0800 Shaohua Li <[email protected]> wrote:
Quoted 61 lines · NeilBrown
Quoted 36 lines · On Wed, 23 May 2012 15:26:20 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If no bitmap, there is no point to queue bio to a thread and dispatch it in the thread. Directly dispatching bio doesn't impact correctness and removes above bottleneck.

Multiple threads dispatch requests could potentially reduce request merge and increase lock contention. For slow stroage, we just worry about request merge. Caller of .make_request should already have correct block plug set, which will take care of request merge and locking just like accessing raw device, so we don't need worry about this too much.

In a 4k randwrite test with a 2 disks setup, below patch can provide 20% ~ 50% performance improvements depending on numa binding.

---
 drivers/md/raid1.c |   11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

Index: linux/drivers/md/raid1.c
===================================================================

drivers/md/raid1.c +0 -4
@@ -1187,10 +1187,13 @@ read_again:
 		mbio->bi_private = r1_bio;
 
 		atomic_inc(&r1_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);

drivers/md/raid1.c read_again: · line 1199

Quoted 10 lines
drivers/md/raid1.c +7 -0
+		if (bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 	}
 	/* Mustn't call r1_bio_write_done before this next test,
 	 * as it could result in the bio being freed.

This looks like it should be 'obviously correct' but unfortunately it isn't.

If this raid1 is beneath a dm device (e.g. LVM), then generic_make_request will queue the request internally and not actually start it.
In particular this means that the cloned bio has no chance of being released before the next clone_bio call which is made on the next time around the loop.

This can lead to a deadlock as the clone_bio() might be waiting for that first cloned bio to be released (if memory is really tight).

i.e. when allocating multiple bios from a mempool, we need to arrange for them to be submitted by a separate thread.

Quoted 8 lines
Quoted 2 lines

If there isn't block plug, generic_make_request will dispatch the request directly.

Not necessarily. It might queue it:

	if (current->bio_list) {
		bio_list_add(current->bio_list, bio);
		return;
	}

Ah, yes. Looks we can workaround it to increase bioset entry number to raid_disks*2. There isn't too much memory wasted with it.

Thanks,
Shaohua

#6 lore raw

Re: [patch 1/4] raid1: directly dispatch write request if no bitmap

Quoted 10 lines · On Thu, 24 May 2012 11:31:26 +0800 Shaohua Li <[email protected]> wrote:
Quoted 5 lines · NeilBrown

Not necessarily. It might queue it:

	if (current->bio_list) {
		bio_list_add(current->bio_list, bio);
		return;
	}

Ah, yes. Looks we can workaround it to increase bioset entry number to raid_disks*2. There isn't too much memory wasted with it.

No, that doesn't work. There might be raid_disk*2 separate threads which have each allocated on entry from the pool, and then they all block waiting for another.

Adding the pool can change the probability of a deadlock but not the possibility.

NeilBrown

author
#7 lore raw

PATCH 2/4 raid1: percpu dispatch for write request if bitmap supported

In reply to Shaohua Li

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If bitmap support is enabled, write requests can only be dispatched after dirty bitmap is flushed out. After bitmap is flushed, how write requests are dispatched doesn't impact correctness. A natural idea is to distribute request dispatch to several threads. With this patch, requests are added to a percpu list first. After bitmap is flushed, then the percpu list requests will dispatched in a workqueue. In this way, above bottleneck is removed.

In a 4k randwrite test with a 2 disks setup, below patch can provide 10% ~ 50% performance improvements depending on numa binding.

---
 drivers/md/raid1.c |   96 +++++++++++++++++++++++++++++++++++++++++++----------
 drivers/md/raid1.h |   10 ++++-
 2 files changed, 87 insertions(+), 19 deletions(-)
drivers/md/raid1.h +9 -1
Index: linux/drivers/md/raid1.h
===================================================================
@@ -22,6 +22,14 @@ struct pool_info {
 	int	raid_disks;
 };
 
+struct write_list {
+	struct bio_list	pending_bio_list;
+	struct bio_list	running_bio_list;
+	struct bio_list	tmp_bio_list;
+	struct work_struct work;
+	struct r1conf *conf;
+};
+
 struct r1conf {
 	struct mddev		*mddev;
 	struct mirror_info	*mirrors;	/* twice 'raid_disks' to
@@ -50,7 +58,7 @@ struct r1conf {
 	struct list_head	retry_list;
 
 	/* queue pending writes to be submitted on unplug */
-	struct bio_list		pending_bio_list;
+	struct write_list __percpu *write_list;
 	int			pending_count;
 
 	/* for use when syncing mirrors:
drivers/md/raid1.c +78 -18

141 lines not on this page · raw message

#8 lore raw

Re: [patch 2/4] raid1: percpu dispatch for write request if bitmap supported

Quoted 13 lines · On Wed, 23 May 2012 15:26:21 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If bitmap support is enabled, write requests can only be dispatched after dirty bitmap is flushed out. After bitmap is flushed, how write requests are dispatched doesn't impact correctness. A natural idea is to distribute request dispatch to several threads. With this patch, requests are added to a percpu list first. After bitmap is flushed, then the percpu list requests will dispatched in a workqueue. In this way, above bottleneck is removed.

Quoted 2 lines

In a 4k randwrite test with a 2 disks setup, below patch can provide 10% ~ 50% performance improvements depending on numa binding.

Those numbers are quite impressive so there is certainly room for improvement here. I'm not sure that I'm entirely comfortable with the approach though.

Passing the request to a per-cpu thread does make sense, but a generic per-cpu thread feels dangerous as we don't know what else might be queued to that thread and because of the potential for deadlocks between memory allocation and generic_make_request (as mentioned in previous email) I find it hard to convince myself that this approach is entirely safe.

I wonder if we might take a very different approach and try to do everything in the one process. i.e. don't hand tasks off to other threads at all - at least in the common case.

So we could change plugger_unplug (in md.c) so that:

- if current->bio_list is NULL, (meaning all requests have been submitted and there is no risk of deadlock) we call bitmap_unplug and then submit all the queued writes.
- if current->bio_list is not NULL, then we just wakeup the md thread to do the work.

In the common case bio_list should be NULL (I think) and everything is done in the one process. It is only when memory is tight and we reschedule while waiting for memory that the md thread will be called on and hopefully slower performance then won't be so noticeable.

We probably want separate queues of pending writes for each CPU (or each thread) so that each "plugger_unplug" only submits the writes that were queued by that process - so that if multiple threads are submitting writes at the same time, they don't end up handling each other's requests.

This might end up flushing the bitmap slightly more often - the current code allocates one "md_plug_cb" for each different thread that is concurrently writing to a given md device, and don't trigger the flush until all of them unplug. My proposal above would trigger it when any thread triggers the final unplug. I wonder if that makes any difference in practice.

Anyway, could you explore the possibility of submitting the requests in plugger_unplug when current->bio_list is NULL?
Possibly you could:
get mddev_check_plugged to return the md_plug_cb
link all the md_plug_cb's for one mddev together
store the list of queued write requests in that md_plug_cb

Make sense?

Thanks,
NeilBrown

author
#9 lore raw

Re: [patch 2/4] raid1: percpu dispatch for write request if bitmap supported

Quoted 18 lines · NeilBrown
Quoted 13 lines · On Wed, 23 May 2012 15:26:21 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If bitmap support is enabled, write requests can only be dispatched after dirty bitmap is flushed out. After bitmap is flushed, how write requests are dispatched doesn't impact correctness. A natural idea is to distribute request dispatch to several threads. With this patch, requests are added to a percpu list first. After bitmap is flushed, then the percpu list requests will dispatched in a workqueue. In this way, above bottleneck is removed.

Quoted 2 lines

In a 4k randwrite test with a 2 disks setup, below patch can provide 10% ~ 50% performance improvements depending on numa binding.

Quoted 8 lines

Those numbers are quite impressive so there is certainly room for improvement here. I'm not sure that I'm entirely comfortable with the approach though.

Passing the request to a per-cpu thread does make sense, but a generic per-cpu thread feels dangerous as we don't know what else might be queued to that thread and because of the potential for deadlocks between memory allocation and generic_make_request (as mentioned in previous email) I find it hard to convince myself that this approach is entirely safe.

No problem, we can use a separate workqueue.

Quoted 10 lines

I wonder if we might take a very different approach and try to do everything in the one process. i.e. don't hand tasks off to other threads at all - at least in the common case.
So we could change plugger_unplug (in md.c) so that:

- if current->bio_list is NULL, (meaning all requests have been submitted and there is no risk of deadlock) we call bitmap_unplug and then submit all the queued writes.
- if current->bio_list is not NULL, then we just wakeup the md thread to do the work.

The current->bio_list check does make sense. I'm going to do the check for the 1 and 3 patches.

But I believe we can't call generic_make_request in unplug. For example, schedule()->unplug->generic_make_request->get_request_wait()->schedule(). At least this will cause some request not dispatch. And last time I did similar experiment for raid0 (request is added to a per-disk plug_cb list, not directly dispatch) to reduce lock contention, I found nasty oops.

Thanks,
Shaohua

#10 lore raw

Re: [patch 2/4] raid1: percpu dispatch for write request if bitmap supported

Quoted 22 lines · On Thu, 24 May 2012 13:17:09 +0800 Shaohua Li <[email protected]> wrote:
Quoted 21 lines · NeilBrown
Quoted 13 lines · On Wed, 23 May 2012 15:26:21 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If bitmap support is enabled, write requests can only be dispatched after dirty bitmap is flushed out. After bitmap is flushed, how write requests are dispatched doesn't impact correctness. A natural idea is to distribute request dispatch to several threads. With this patch, requests are added to a percpu list first. After bitmap is flushed, then the percpu list requests will dispatched in a workqueue. In this way, above bottleneck is removed.

Quoted 2 lines

In a 4k randwrite test with a 2 disks setup, below patch can provide 10% ~ 50% performance improvements depending on numa binding.

Those numbers are quite impressive so there is certainly room for improvement here. I'm not sure that I'm entirely comfortable with the approach though.

Quoted 7 lines
Quoted 5 lines

Passing the request to a per-cpu thread does make sense, but a generic per-cpu thread feels dangerous as we don't know what else might be queued to that thread and because of the potential for deadlocks between memory allocation and generic_make_request (as mentioned in previous email) I find it hard to convince myself that this approach is entirely safe.

No problem, we can use a separate workqueue.

A separate set of per-cpu workqueues for each md array doesn't sound like a good idea to me - if we can possibly avoid it.

Quoted 12 lines
Quoted 11 lines

I wonder if we might take a very different approach and try to do everything in the one process. i.e. don't hand tasks off to other threads at all - at least in the common case.
So we could change plugger_unplug (in md.c) so that:

- if current->bio_list is NULL, (meaning all requests have been submitted and there is no risk of deadlock) we call bitmap_unplug and then submit all the queued writes.
- if current->bio_list is not NULL, then we just wakeup the md thread to do the work.

Quoted 5 lines

The current->bio_list check does make sense. I'm going to do the check for the 1 and 3 patches.

But I believe we can't call generic_make_request in unplug. For example, schedule()->unplug->generic_make_request->get_request_wait()->schedule(). At

My ideas was to only submit the queued writes from a normal call to blk_finish_plug. Calls that come from schedule would use the current approach of signalling the md thread to take over. There must be a way to tell the difference - maybe just a new flag to the unplug callback.

NeilBrown

Quoted 6 lines

least this will cause some request not dispatch. And last time I did similar experiment for raid0 (request is added to a per-disk plug_cb list, not directly dispatch) to reduce lock contention, I found nasty oops.

Thanks,
Shaohua

author
#11 lore raw

Re: [patch 2/4] raid1: percpu dispatch for write request if bitmap supported

Quoted 30 lines · NeilBrown
Quoted 28 lines · On Thu, 24 May 2012 13:17:09 +0800 Shaohua Li <[email protected]> wrote:
Quoted 26 lines · NeilBrown
Quoted 13 lines · On Wed, 23 May 2012 15:26:21 +0800 Shaohua Li <[email protected]> wrote:

In raid1, all write requests are dispatched in raid1d thread. In fast storage, the raid1d thread is a bottleneck, because it dispatches request too slow. Also raid1d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If bitmap support is enabled, write requests can only be dispatched after dirty bitmap is flushed out. After bitmap is flushed, how write requests are dispatched doesn't impact correctness. A natural idea is to distribute request dispatch to several threads. With this patch, requests are added to a percpu list first. After bitmap is flushed, then the percpu list requests will dispatched in a workqueue. In this way, above bottleneck is removed.

Quoted 2 lines

In a 4k randwrite test with a 2 disks setup, below patch can provide 10% ~ 50% performance improvements depending on numa binding.

Those numbers are quite impressive so there is certainly room for improvement here. I'm not sure that I'm entirely comfortable with the approach though.

Passing the request to a per-cpu thread does make sense, but a generic per-cpu thread feels dangerous as we don't know what else might be queued to that thread and because of the potential for deadlocks between memory allocation and generic_make_request (as mentioned in previous email) I find it hard to convince myself that this approach is entirely safe.

Quoted 4 lines
Quoted 1 line

No problem, we can use a separate workqueue.

A separate set of per-cpu workqueues for each md array doesn't sound like a good idea to me - if we can possibly avoid it.

Can be a separate workqueue sharing for all md arrays.

Quoted 14 lines
Quoted 14 lines
Quoted 10 lines

I wonder if we might take a very different approach and try to do everything in the one process. i.e. don't hand tasks off to other threads at all - at least in the common case.
So we could change plugger_unplug (in md.c) so that:

- if current->bio_list is NULL, (meaning all requests have been submitted and there is no risk of deadlock) we call bitmap_unplug and then submit all the queued writes.
- if current->bio_list is not NULL, then we just wakeup the md thread to do the work.

The current->bio_list check does make sense. I'm going to do the check for the 1 and 3 patches.

Quoted 7 lines
Quoted 2 lines

But I believe we can't call generic_make_request in unplug. For example, schedule()->unplug->generic_make_request->get_request_wait()->schedule(). At

My ideas was to only submit the queued writes from a normal call to blk_finish_plug. Calls that come from schedule would use the current approach of signalling the md thread to take over. There must be a way to tell the difference - maybe just a new flag to the unplug callback.

Should be the same I think. For exmaple, blk_finish_plug unplug, plug_cb should generate several bios, but for the first bio, get_request_wait sleeps and flush plug list again. later bio hasn't a chance to be queued actually till next time the task runs. Then unplug loses its meaning.

Thanks,
Shaohua

author
#12 lore raw

PATCH 3/4 raid10: directly dispatch write request if no bitmap

In reply to Shaohua Li

In raid10, all write requests are dispatched in raid10d thread. In fast storage, the raid10d thread is a bottleneck, because it dispatches request too slow. Also raid10d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If no bitmap, there is no point to queue bio to a thread and dispatch it in the thread. Directly dispatching bio doesn't impact correctness and removes above bottleneck.

Multiple threads dispatch requests could potentially reduce request merge and increase lock contention. For slow stroage, we just worry about request merge. Caller of .make_request should already have correct block plug set, which will take care of request merge and locking just like accessing raw device, so we don't need worry about this too much.

In a 4k randwrite test with a 4 disks setup, below patch can provide 95% ~ 135% performance improvements depending on numa binding.

---
 drivers/md/raid10.c |   22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)
drivers/md/raid10.c +14 -8
Index: linux/drivers/md/raid10.c
===================================================================
@@ -1304,10 +1304,13 @@ retry_write:
 		mbio->bi_private = r10_bio;
 
 		atomic_inc(&r10_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);
+		if (mddev->bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 
 		if (!r10_bio->devs[i].repl_bio)
 			continue;
@@ -1329,10 +1332,13 @@ retry_write:
 		mbio->bi_private = r10_bio;
 
 		atomic_inc(&r10_bio->remaining);
-		spin_lock_irqsave(&conf->device_lock, flags);
-		bio_list_add(&conf->pending_bio_list, mbio);
-		conf->pending_count++;
-		spin_unlock_irqrestore(&conf->device_lock, flags);
+		if (mddev->bitmap) {
+			spin_lock_irqsave(&conf->device_lock, flags);
+			bio_list_add(&conf->pending_bio_list, mbio);
+			conf->pending_count++;
+			spin_unlock_irqrestore(&conf->device_lock, flags);
+		} else
+			generic_make_request(mbio);
 	}
 
 	/* Don't remove the bias on 'remaining' (one_write_done) until
author
#13 lore raw

PATCH 4/4 raid10: percpu dispatch for write request if bitmap supported

In reply to Shaohua Li

In raid10, all write requests are dispatched in raid10d thread. In fast storage, the raid10d thread is a bottleneck, because it dispatches request too slow. Also raid10d thread migrates freely, which makes request completion cpu not match with submission cpu even driver/block layer has such capability. This will cause bad cache issue.

If bitmap support is enabled, write requests can only be dispatched after dirty bitmap is flushed out. After bitmap is flushed, how write requests are dispatched doesn't impact correctness. A natural idea is to distribute request dispatch to several threads. With this patch, requests are added to a percpu list first. After bitmap is flushed, then the percpu list requests will dispatched in a workqueue. In this way, above bottleneck is removed.

In a 4k randwrite test with a 4 disks setup, below patch can provide about 95% performance improvements depending on numa binding.

---
 drivers/md/raid10.c |   97 ++++++++++++++++++++++++++++++++++++++++++----------
 drivers/md/raid10.h |   10 ++++-
 2 files changed, 88 insertions(+), 19 deletions(-)
drivers/md/raid10.h +9 -1
Index: linux/drivers/md/raid10.h
===================================================================
@@ -11,6 +11,14 @@ struct mirror_info {
 						 */
 };
 
+struct write_list {
+	struct bio_list	pending_bio_list;
+	struct bio_list	running_bio_list;
+	struct bio_list	tmp_bio_list;
+	struct work_struct work;
+	struct r10conf *conf;
+};
+
 struct r10conf {
 	struct mddev		*mddev;
 	struct mirror_info	*mirrors;
@@ -43,7 +51,7 @@ struct r10conf {
 
 	struct list_head	retry_list;
 	/* queue pending writes and submit them on unplug */
-	struct bio_list		pending_bio_list;
+	struct write_list __percpu *write_list;
 	int			pending_count;
 
 	spinlock_t		resync_lock;
drivers/md/raid10.c +79 -18

143 lines not on this page · raw message