diff mbox series

[2/7] blk-throttle: Refactor tg_dispatch_time by extracting tg_dispatch_bps/iops_time

Message ID 20250414132731.167620-3-wozizhi@huawei.com (mailing list archive)
State New
Headers show
Series blk-throttle: Split the blkthrotl queue to solve the IO delay issue | expand

Commit Message

Zizhi Wo April 14, 2025, 1:27 p.m. UTC
tg_dispatch_time() contained both bps and iops throttling logic. We now
split its internal logic into tg_dispatch_bps/iops_time() to improve code
consistency for future separation of the bps and iops queues.

Besides, merge time_before() from caller into throtl_extend_slice() to make
code cleaner.

Signed-off-by: Zizhi Wo <wozizhi@huawei.com>
---
 block/blk-throttle.c | 98 +++++++++++++++++++++++++-------------------
 1 file changed, 55 insertions(+), 43 deletions(-)

Comments

Yu Kuai April 15, 2025, 2:19 a.m. UTC | #1
Hi, one nit below:

在 2025/04/14 21:27, Zizhi Wo 写道:
> tg_dispatch_time() contained both bps and iops throttling logic. We now
> split its internal logic into tg_dispatch_bps/iops_time() to improve code
> consistency for future separation of the bps and iops queues.
> 
> Besides, merge time_before() from caller into throtl_extend_slice() to make
> code cleaner.
> 
> Signed-off-by: Zizhi Wo <wozizhi@huawei.com>
> ---
>   block/blk-throttle.c | 98 +++++++++++++++++++++++++-------------------
>   1 file changed, 55 insertions(+), 43 deletions(-)
> 
> diff --git a/block/blk-throttle.c b/block/blk-throttle.c
> index dc23c961c028..0633ae0cce90 100644
> --- a/block/blk-throttle.c
> +++ b/block/blk-throttle.c
> @@ -520,6 +520,9 @@ static inline void throtl_set_slice_end(struct throtl_grp *tg, bool rw,
>   static inline void throtl_extend_slice(struct throtl_grp *tg, bool rw,
>   				       unsigned long jiffy_end)
>   {
> +	if (!time_before(tg->slice_end[rw], jiffy_end))
> +		return;
> +
>   	throtl_set_slice_end(tg, rw, jiffy_end);
>   	throtl_log(&tg->service_queue,
>   		   "[%c] extend slice start=%lu end=%lu jiffies=%lu",
> @@ -682,10 +685,6 @@ static unsigned long tg_within_iops_limit(struct throtl_grp *tg, struct bio *bio
>   	int io_allowed;
>   	unsigned long jiffy_elapsed, jiffy_wait, jiffy_elapsed_rnd;
>   
> -	if (iops_limit == UINT_MAX) {
> -		return 0;
> -	}
> -
>   	jiffy_elapsed = jiffies - tg->slice_start[rw];
>   
>   	/* Round up to the next throttle slice, wait time must be nonzero */
> @@ -711,11 +710,6 @@ static unsigned long tg_within_bps_limit(struct throtl_grp *tg, struct bio *bio,
>   	unsigned long jiffy_elapsed, jiffy_wait, jiffy_elapsed_rnd;
>   	unsigned int bio_size = throtl_bio_data_size(bio);
>   
> -	/* no need to throttle if this bio's bytes have been accounted */
> -	if (bps_limit == U64_MAX || bio_flagged(bio, BIO_BPS_THROTTLED)) {
> -		return 0;
> -	}
> -
>   	jiffy_elapsed = jiffy_elapsed_rnd = jiffies - tg->slice_start[rw];
>   
>   	/* Slice has just started. Consider one slice interval */
> @@ -742,6 +736,54 @@ static unsigned long tg_within_bps_limit(struct throtl_grp *tg, struct bio *bio,
>   	return jiffy_wait;
>   }
>   
> +/*
> + * If previous slice expired, start a new one otherwise renew/extend existing
> + * slice to make sure it is at least throtl_slice interval long since now.
> + * New slice is started only for empty throttle group. If there is queued bio,
> + * that means there should be an active slice and it should be extended instead.
> + */
> +static void tg_update_slice(struct throtl_grp *tg, bool rw)
> +{
> +	if (throtl_slice_used(tg, rw) && !(tg->service_queue.nr_queued[rw]))
> +		throtl_start_new_slice(tg, rw, true);
> +	else
> +		throtl_extend_slice(tg, rw, jiffies + tg->td->throtl_slice);
> +}
> +
> +static unsigned long tg_dispatch_bps_time(struct throtl_grp *tg, struct bio *bio)
> +{
> +	bool rw = bio_data_dir(bio);
> +	u64 bps_limit = tg_bps_limit(tg, rw);
> +	unsigned long bps_wait;
> +
> +	/* no need to throttle if this bio's bytes have been accounted */
> +	if (bps_limit == U64_MAX || tg->flags & THROTL_TG_CANCELING ||
> +	    bio_flagged(bio, BIO_BPS_THROTTLED))
> +		return 0;
> +
> +	tg_update_slice(tg, rw);
> +	bps_wait = tg_within_bps_limit(tg, bio, bps_limit);

if (bps_wait > 0)
> +	throtl_extend_slice(tg, rw, jiffies + bps_wait);
> +
> +	return bps_wait;
> +}
> +
> +static unsigned long tg_dispatch_iops_time(struct throtl_grp *tg, struct bio *bio)
> +{
> +	bool rw = bio_data_dir(bio);
> +	u32 iops_limit = tg_iops_limit(tg, rw);
> +	unsigned long iops_wait;
> +
> +	if (iops_limit == UINT_MAX || tg->flags & THROTL_TG_CANCELING)
> +		return 0;
> +
> +	tg_update_slice(tg, rw);
> +	iops_wait = tg_within_iops_limit(tg, bio, iops_limit);

if (iops_wait > 0)

Otherwise, LGTM

Thanks,
Kuai
> +	throtl_extend_slice(tg, rw, jiffies + iops_wait);
> +
> +	return iops_wait;
> +}
> +
>   /*
>    * Returns approx number of jiffies to wait before this bio is with-in IO rate
>    * and can be dispatched.
> @@ -749,9 +791,7 @@ static unsigned long tg_within_bps_limit(struct throtl_grp *tg, struct bio *bio,
>   static unsigned long tg_dispatch_time(struct throtl_grp *tg, struct bio *bio)
>   {
>   	bool rw = bio_data_dir(bio);
> -	unsigned long bps_wait, iops_wait, max_wait;
> -	u64 bps_limit = tg_bps_limit(tg, rw);
> -	u32 iops_limit = tg_iops_limit(tg, rw);
> +	unsigned long bps_wait, iops_wait;
>   
>   	/*
>    	 * Currently whole state machine of group depends on first bio
> @@ -762,38 +802,10 @@ static unsigned long tg_dispatch_time(struct throtl_grp *tg, struct bio *bio)
>   	BUG_ON(tg->service_queue.nr_queued[rw] &&
>   	       bio != throtl_peek_queued(&tg->service_queue.queued[rw]));
>   
> -	/* If tg->bps = -1, then BW is unlimited */
> -	if ((bps_limit == U64_MAX && iops_limit == UINT_MAX) ||
> -	    tg->flags & THROTL_TG_CANCELING)
> -		return 0;
> -
> -	/*
> -	 * If previous slice expired, start a new one otherwise renew/extend
> -	 * existing slice to make sure it is at least throtl_slice interval
> -	 * long since now. New slice is started only for empty throttle group.
> -	 * If there is queued bio, that means there should be an active
> -	 * slice and it should be extended instead.
> -	 */
> -	if (throtl_slice_used(tg, rw) && !(tg->service_queue.nr_queued[rw]))
> -		throtl_start_new_slice(tg, rw, true);
> -	else {
> -		if (time_before(tg->slice_end[rw],
> -		    jiffies + tg->td->throtl_slice))
> -			throtl_extend_slice(tg, rw,
> -				jiffies + tg->td->throtl_slice);
> -	}
> -
> -	bps_wait = tg_within_bps_limit(tg, bio, bps_limit);
> -	iops_wait = tg_within_iops_limit(tg, bio, iops_limit);
> -	if (bps_wait + iops_wait == 0)
> -		return 0;
> -
> -	max_wait = max(bps_wait, iops_wait);
> -
> -	if (time_before(tg->slice_end[rw], jiffies + max_wait))
> -		throtl_extend_slice(tg, rw, jiffies + max_wait);
> +	bps_wait = tg_dispatch_bps_time(tg, bio);
> +	iops_wait = tg_dispatch_iops_time(tg, bio);
>   
> -	return max_wait;
> +	return max(bps_wait, iops_wait);
>   }
>   
>   static void throtl_charge_bio(struct throtl_grp *tg, struct bio *bio)
>
Zizhi Wo April 15, 2025, 6:13 a.m. UTC | #2
在 2025/4/15 10:19, Yu Kuai 写道:
> Hi, one nit below:
> 
> 在 2025/04/14 21:27, Zizhi Wo 写道:
>> tg_dispatch_time() contained both bps and iops throttling logic. We now
>> split its internal logic into tg_dispatch_bps/iops_time() to improve code
>> consistency for future separation of the bps and iops queues.
>>
>> Besides, merge time_before() from caller into throtl_extend_slice() to 
>> make
>> code cleaner.
>>
>> Signed-off-by: Zizhi Wo <wozizhi@huawei.com>
>> ---
>>   block/blk-throttle.c | 98 +++++++++++++++++++++++++-------------------
>>   1 file changed, 55 insertions(+), 43 deletions(-)
>>
>> diff --git a/block/blk-throttle.c b/block/blk-throttle.c
>> index dc23c961c028..0633ae0cce90 100644
>> --- a/block/blk-throttle.c
>> +++ b/block/blk-throttle.c
>> @@ -520,6 +520,9 @@ static inline void throtl_set_slice_end(struct 
>> throtl_grp *tg, bool rw,
>>   static inline void throtl_extend_slice(struct throtl_grp *tg, bool rw,
>>                          unsigned long jiffy_end)
>>   {
>> +    if (!time_before(tg->slice_end[rw], jiffy_end))
>> +        return;
>> +
>>       throtl_set_slice_end(tg, rw, jiffy_end);
>>       throtl_log(&tg->service_queue,
>>              "[%c] extend slice start=%lu end=%lu jiffies=%lu",
>> @@ -682,10 +685,6 @@ static unsigned long tg_within_iops_limit(struct 
>> throtl_grp *tg, struct bio *bio
>>       int io_allowed;
>>       unsigned long jiffy_elapsed, jiffy_wait, jiffy_elapsed_rnd;
>> -    if (iops_limit == UINT_MAX) {
>> -        return 0;
>> -    }
>> -
>>       jiffy_elapsed = jiffies - tg->slice_start[rw];
>>       /* Round up to the next throttle slice, wait time must be 
>> nonzero */
>> @@ -711,11 +710,6 @@ static unsigned long tg_within_bps_limit(struct 
>> throtl_grp *tg, struct bio *bio,
>>       unsigned long jiffy_elapsed, jiffy_wait, jiffy_elapsed_rnd;
>>       unsigned int bio_size = throtl_bio_data_size(bio);
>> -    /* no need to throttle if this bio's bytes have been accounted */
>> -    if (bps_limit == U64_MAX || bio_flagged(bio, BIO_BPS_THROTTLED)) {
>> -        return 0;
>> -    }
>> -
>>       jiffy_elapsed = jiffy_elapsed_rnd = jiffies - tg->slice_start[rw];
>>       /* Slice has just started. Consider one slice interval */
>> @@ -742,6 +736,54 @@ static unsigned long tg_within_bps_limit(struct 
>> throtl_grp *tg, struct bio *bio,
>>       return jiffy_wait;
>>   }
>> +/*
>> + * If previous slice expired, start a new one otherwise renew/extend 
>> existing
>> + * slice to make sure it is at least throtl_slice interval long since 
>> now.
>> + * New slice is started only for empty throttle group. If there is 
>> queued bio,
>> + * that means there should be an active slice and it should be 
>> extended instead.
>> + */
>> +static void tg_update_slice(struct throtl_grp *tg, bool rw)
>> +{
>> +    if (throtl_slice_used(tg, rw) && !(tg->service_queue.nr_queued[rw]))
>> +        throtl_start_new_slice(tg, rw, true);
>> +    else
>> +        throtl_extend_slice(tg, rw, jiffies + tg->td->throtl_slice);
>> +}
>> +
>> +static unsigned long tg_dispatch_bps_time(struct throtl_grp *tg, 
>> struct bio *bio)
>> +{
>> +    bool rw = bio_data_dir(bio);
>> +    u64 bps_limit = tg_bps_limit(tg, rw);
>> +    unsigned long bps_wait;
>> +
>> +    /* no need to throttle if this bio's bytes have been accounted */
>> +    if (bps_limit == U64_MAX || tg->flags & THROTL_TG_CANCELING ||
>> +        bio_flagged(bio, BIO_BPS_THROTTLED))
>> +        return 0;
>> +
>> +    tg_update_slice(tg, rw);
>> +    bps_wait = tg_within_bps_limit(tg, bio, bps_limit);
> 
> if (bps_wait > 0)

Since the time_before() logic has been incorporated into
throtl_extend_slice(), can we simplify the code by not adding the extra
check?

Thanks,
Zizhi Wo

>> +    throtl_extend_slice(tg, rw, jiffies + bps_wait);
>> +
>> +    return bps_wait;
>> +}
>> +
>> +static unsigned long tg_dispatch_iops_time(struct throtl_grp *tg, 
>> struct bio *bio)
>> +{
>> +    bool rw = bio_data_dir(bio);
>> +    u32 iops_limit = tg_iops_limit(tg, rw);
>> +    unsigned long iops_wait;
>> +
>> +    if (iops_limit == UINT_MAX || tg->flags & THROTL_TG_CANCELING)
>> +        return 0;
>> +
>> +    tg_update_slice(tg, rw);
>> +    iops_wait = tg_within_iops_limit(tg, bio, iops_limit);
> 
> if (iops_wait > 0)
> 
> Otherwise, LGTM
> 
> Thanks,
> Kuai
>> +    throtl_extend_slice(tg, rw, jiffies + iops_wait);
>> +
>> +    return iops_wait;
>> +}
>> +
>>   /*
>>    * Returns approx number of jiffies to wait before this bio is 
>> with-in IO rate
>>    * and can be dispatched.
>> @@ -749,9 +791,7 @@ static unsigned long tg_within_bps_limit(struct 
>> throtl_grp *tg, struct bio *bio,
>>   static unsigned long tg_dispatch_time(struct throtl_grp *tg, struct 
>> bio *bio)
>>   {
>>       bool rw = bio_data_dir(bio);
>> -    unsigned long bps_wait, iops_wait, max_wait;
>> -    u64 bps_limit = tg_bps_limit(tg, rw);
>> -    u32 iops_limit = tg_iops_limit(tg, rw);
>> +    unsigned long bps_wait, iops_wait;
>>       /*
>>         * Currently whole state machine of group depends on first bio
>> @@ -762,38 +802,10 @@ static unsigned long tg_dispatch_time(struct 
>> throtl_grp *tg, struct bio *bio)
>>       BUG_ON(tg->service_queue.nr_queued[rw] &&
>>              bio != throtl_peek_queued(&tg->service_queue.queued[rw]));
>> -    /* If tg->bps = -1, then BW is unlimited */
>> -    if ((bps_limit == U64_MAX && iops_limit == UINT_MAX) ||
>> -        tg->flags & THROTL_TG_CANCELING)
>> -        return 0;
>> -
>> -    /*
>> -     * If previous slice expired, start a new one otherwise renew/extend
>> -     * existing slice to make sure it is at least throtl_slice interval
>> -     * long since now. New slice is started only for empty throttle 
>> group.
>> -     * If there is queued bio, that means there should be an active
>> -     * slice and it should be extended instead.
>> -     */
>> -    if (throtl_slice_used(tg, rw) && !(tg->service_queue.nr_queued[rw]))
>> -        throtl_start_new_slice(tg, rw, true);
>> -    else {
>> -        if (time_before(tg->slice_end[rw],
>> -            jiffies + tg->td->throtl_slice))
>> -            throtl_extend_slice(tg, rw,
>> -                jiffies + tg->td->throtl_slice);
>> -    }
>> -
>> -    bps_wait = tg_within_bps_limit(tg, bio, bps_limit);
>> -    iops_wait = tg_within_iops_limit(tg, bio, iops_limit);
>> -    if (bps_wait + iops_wait == 0)
>> -        return 0;
>> -
>> -    max_wait = max(bps_wait, iops_wait);
>> -
>> -    if (time_before(tg->slice_end[rw], jiffies + max_wait))
>> -        throtl_extend_slice(tg, rw, jiffies + max_wait);
>> +    bps_wait = tg_dispatch_bps_time(tg, bio);
>> +    iops_wait = tg_dispatch_iops_time(tg, bio);
>> -    return max_wait;
>> +    return max(bps_wait, iops_wait);
>>   }
>>   static void throtl_charge_bio(struct throtl_grp *tg, struct bio *bio)
>>
>
Yu Kuai April 15, 2025, 6:26 a.m. UTC | #3
Hi,

在 2025/04/15 14:13, Zizhi Wo 写道:
> Since the time_before() logic has been incorporated into
> throtl_extend_slice(), can we simplify the code by not adding the extra
> check?

Yes, I missed the early return in throtl_extend_slice().

Feel free to add:
Reviewed-by: Yu Kuai <yukuai3@huawei.com>
diff mbox series

Patch

diff --git a/block/blk-throttle.c b/block/blk-throttle.c
index dc23c961c028..0633ae0cce90 100644
--- a/block/blk-throttle.c
+++ b/block/blk-throttle.c
@@ -520,6 +520,9 @@  static inline void throtl_set_slice_end(struct throtl_grp *tg, bool rw,
 static inline void throtl_extend_slice(struct throtl_grp *tg, bool rw,
 				       unsigned long jiffy_end)
 {
+	if (!time_before(tg->slice_end[rw], jiffy_end))
+		return;
+
 	throtl_set_slice_end(tg, rw, jiffy_end);
 	throtl_log(&tg->service_queue,
 		   "[%c] extend slice start=%lu end=%lu jiffies=%lu",
@@ -682,10 +685,6 @@  static unsigned long tg_within_iops_limit(struct throtl_grp *tg, struct bio *bio
 	int io_allowed;
 	unsigned long jiffy_elapsed, jiffy_wait, jiffy_elapsed_rnd;
 
-	if (iops_limit == UINT_MAX) {
-		return 0;
-	}
-
 	jiffy_elapsed = jiffies - tg->slice_start[rw];
 
 	/* Round up to the next throttle slice, wait time must be nonzero */
@@ -711,11 +710,6 @@  static unsigned long tg_within_bps_limit(struct throtl_grp *tg, struct bio *bio,
 	unsigned long jiffy_elapsed, jiffy_wait, jiffy_elapsed_rnd;
 	unsigned int bio_size = throtl_bio_data_size(bio);
 
-	/* no need to throttle if this bio's bytes have been accounted */
-	if (bps_limit == U64_MAX || bio_flagged(bio, BIO_BPS_THROTTLED)) {
-		return 0;
-	}
-
 	jiffy_elapsed = jiffy_elapsed_rnd = jiffies - tg->slice_start[rw];
 
 	/* Slice has just started. Consider one slice interval */
@@ -742,6 +736,54 @@  static unsigned long tg_within_bps_limit(struct throtl_grp *tg, struct bio *bio,
 	return jiffy_wait;
 }
 
+/*
+ * If previous slice expired, start a new one otherwise renew/extend existing
+ * slice to make sure it is at least throtl_slice interval long since now.
+ * New slice is started only for empty throttle group. If there is queued bio,
+ * that means there should be an active slice and it should be extended instead.
+ */
+static void tg_update_slice(struct throtl_grp *tg, bool rw)
+{
+	if (throtl_slice_used(tg, rw) && !(tg->service_queue.nr_queued[rw]))
+		throtl_start_new_slice(tg, rw, true);
+	else
+		throtl_extend_slice(tg, rw, jiffies + tg->td->throtl_slice);
+}
+
+static unsigned long tg_dispatch_bps_time(struct throtl_grp *tg, struct bio *bio)
+{
+	bool rw = bio_data_dir(bio);
+	u64 bps_limit = tg_bps_limit(tg, rw);
+	unsigned long bps_wait;
+
+	/* no need to throttle if this bio's bytes have been accounted */
+	if (bps_limit == U64_MAX || tg->flags & THROTL_TG_CANCELING ||
+	    bio_flagged(bio, BIO_BPS_THROTTLED))
+		return 0;
+
+	tg_update_slice(tg, rw);
+	bps_wait = tg_within_bps_limit(tg, bio, bps_limit);
+	throtl_extend_slice(tg, rw, jiffies + bps_wait);
+
+	return bps_wait;
+}
+
+static unsigned long tg_dispatch_iops_time(struct throtl_grp *tg, struct bio *bio)
+{
+	bool rw = bio_data_dir(bio);
+	u32 iops_limit = tg_iops_limit(tg, rw);
+	unsigned long iops_wait;
+
+	if (iops_limit == UINT_MAX || tg->flags & THROTL_TG_CANCELING)
+		return 0;
+
+	tg_update_slice(tg, rw);
+	iops_wait = tg_within_iops_limit(tg, bio, iops_limit);
+	throtl_extend_slice(tg, rw, jiffies + iops_wait);
+
+	return iops_wait;
+}
+
 /*
  * Returns approx number of jiffies to wait before this bio is with-in IO rate
  * and can be dispatched.
@@ -749,9 +791,7 @@  static unsigned long tg_within_bps_limit(struct throtl_grp *tg, struct bio *bio,
 static unsigned long tg_dispatch_time(struct throtl_grp *tg, struct bio *bio)
 {
 	bool rw = bio_data_dir(bio);
-	unsigned long bps_wait, iops_wait, max_wait;
-	u64 bps_limit = tg_bps_limit(tg, rw);
-	u32 iops_limit = tg_iops_limit(tg, rw);
+	unsigned long bps_wait, iops_wait;
 
 	/*
  	 * Currently whole state machine of group depends on first bio
@@ -762,38 +802,10 @@  static unsigned long tg_dispatch_time(struct throtl_grp *tg, struct bio *bio)
 	BUG_ON(tg->service_queue.nr_queued[rw] &&
 	       bio != throtl_peek_queued(&tg->service_queue.queued[rw]));
 
-	/* If tg->bps = -1, then BW is unlimited */
-	if ((bps_limit == U64_MAX && iops_limit == UINT_MAX) ||
-	    tg->flags & THROTL_TG_CANCELING)
-		return 0;
-
-	/*
-	 * If previous slice expired, start a new one otherwise renew/extend
-	 * existing slice to make sure it is at least throtl_slice interval
-	 * long since now. New slice is started only for empty throttle group.
-	 * If there is queued bio, that means there should be an active
-	 * slice and it should be extended instead.
-	 */
-	if (throtl_slice_used(tg, rw) && !(tg->service_queue.nr_queued[rw]))
-		throtl_start_new_slice(tg, rw, true);
-	else {
-		if (time_before(tg->slice_end[rw],
-		    jiffies + tg->td->throtl_slice))
-			throtl_extend_slice(tg, rw,
-				jiffies + tg->td->throtl_slice);
-	}
-
-	bps_wait = tg_within_bps_limit(tg, bio, bps_limit);
-	iops_wait = tg_within_iops_limit(tg, bio, iops_limit);
-	if (bps_wait + iops_wait == 0)
-		return 0;
-
-	max_wait = max(bps_wait, iops_wait);
-
-	if (time_before(tg->slice_end[rw], jiffies + max_wait))
-		throtl_extend_slice(tg, rw, jiffies + max_wait);
+	bps_wait = tg_dispatch_bps_time(tg, bio);
+	iops_wait = tg_dispatch_iops_time(tg, bio);
 
-	return max_wait;
+	return max(bps_wait, iops_wait);
 }
 
 static void throtl_charge_bio(struct throtl_grp *tg, struct bio *bio)