Skip to contentSkip to conversation

Priorities in Anticipatory I/O scheduler

PATCH In review [email protected] 2 participants 3 messages Jul 6, 2008 – Jul 10, 2008 1 message not in the archive

Subsystems (2)
lore
Share & tools
lore

A message that is not in the archive (sent off-list or not yet received): [email protected]

#1 lore raw

PATCH 1/1 Priorities in Anticipatory I/O scheduler

Modifications to the Anticipatory I/O scheduler to add multiple priority levels. It makes use of anticipation and batching in current anticipatory scheduler to implement priorities.

- Minimizes the latency of highest priority level.
- Low priority requests wait for high priority requests.
- Higher priority request break any anticipating low priority request.
- If single priority level is used the scheduler behaves as an anticipatory scheduler. So no change for existing users.

With this change, it is possible for a latency sensitive job to coexist with background job.

Other possible use of this patch is in context of I/O subsystem controller. It can add another dimension to the parameters controlling a particular cgroup. While we can easily divide b/w among existing croups, setting a bound on latency is not a feasible solution. Hence in context of storage devices bandwidth and priority can be two parameters controlling I/O.

In this patch I have added a new class IOPRIO_CLASS_LATENCY to differentiate notion of absolute priority over existing uses of various time-slice based priority classes in cfq. Though internally within anticipatory scheduler all of them map to best-effort levels. Hence, one can also use various best-effort priority levels.

Signed-off by: Naveen Gupta <[email protected]>
---

block/Kconfig.iosched +8 -0
Index: b/block/Kconfig.iosched
===================================================================
@@ -21,6 +21,14 @@ config IOSCHED_AS
 	  deadline I/O scheduler, it can also be slower in some cases
 	  especially some database loads.
 
+config IOPRIO_AS_MAX
+	int "Number of valid i/o priority levels"
+	depends on IOSCHED_AS
+	default "4"
+	help
+	  This option controls number of priority levels in anticipatory
+	  I/O scheduler.
+
 config IOSCHED_DEADLINE
 	tristate "Deadline I/O scheduler"
 	default y
block/as-iosched.c +282 -50

585 lines not on this page · raw message

include/linux/bio.h +0 -1
Index: b/include/linux/bio.h
===================================================================
@@ -161,7 +161,6 @@ struct bio {
 #define bio_prio_valid(bio)	ioprio_valid(bio_prio(bio))
 
 #define bio_set_prio(bio, prio)		do {			\
-	WARN_ON(prio >= (1 << IOPRIO_BITS));			\
 	(bio)->bi_rw &= ((1UL << BIO_PRIO_SHIFT) - 1);		\
 	(bio)->bi_rw |= ((unsigned long) (prio) << BIO_PRIO_SHIFT);	\
 } while (0)
include/linux/ioprio.h +5 -0
Index: b/include/linux/ioprio.h
===================================================================
@@ -28,6 +28,7 @@ enum {
 	IOPRIO_CLASS_RT,
 	IOPRIO_CLASS_BE,
 	IOPRIO_CLASS_IDLE,
+	IOPRIO_CLASS_LATENCY,
 };
 
 /*
@@ -35,6 +36,10 @@ enum {
  */
 #define IOPRIO_BE_NR	(8)
 
+#define IOPRIO_AS_MAX  CONFIG_IOPRIO_AS_MAX
+
+#define IOPRIO_AS_DEFAULT      IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 2)
+
 enum {
 	IOPRIO_WHO_PROCESS = 1,
 	IOPRIO_WHO_PGRP,
block/blk-core.c +4 -0
Index: b/block/blk-core.c
===================================================================
@@ -1477,6 +1477,10 @@ void submit_bio(int rw, struct bio *bio)
 
 	bio->bi_rw |= rw;
 
+	if (current->io_context)
+		bio_set_prio(bio, current->io_context->ioprio);
+
+
 	/*
 	 * If it's a regular read/write or a barrier with data attached,
 	 * go through the normal accounting stuff before submission.
fs/ioprio.c +1 -0
Index: b/fs/ioprio.c
===================================================================
@@ -180,6 +180,7 @@ int ioprio_best(unsigned short aprio, un
 	else
 		return aprio;
 }
+EXPORT_SYMBOL(ioprio_best);
 
 asmlinkage long sys_ioprio_get(int which, int who)
 {
#2 lore raw

Hi Naveen,

I have a few observations after a quick read through your patch.

Quoted 19 lines · [email protected] wrote:

Modifications to the Anticipatory I/O scheduler to add multiple priority levels. It makes use of anticipation and batching in current anticipatory scheduler to implement priorities.

- Minimizes the latency of highest priority level.
- Low priority requests wait for high priority requests.
- Higher priority request break any anticipating low priority request.
- If single priority level is used the scheduler behaves as an anticipatory scheduler. So no change for existing users.

With this change, it is possible for a latency sensitive job to coexist with background job.

Other possible use of this patch is in context of I/O subsystem controller. It can add another dimension to the parameters controlling a particular cgroup. While we can easily divide b/w among existing croups, setting a bound on latency is not a feasible solution. Hence in context of storage devices bandwidth and priority can be two parameters controlling I/O.

Quoted 5 lines

In this patch I have added a new class IOPRIO_CLASS_LATENCY to differentiate notion of absolute priority over existing uses of various time-slice based priority classes in cfq. Though internally within anticipatory scheduler all of them map to best-effort levels. Hence, one can also use various best-effort priority levels.

I don't see the point of this new priority class; I think ``latency sensitive'' is a reasonable definition of real-time. Especially since you don't actually use it.

block/Kconfig.iosched config IOSCHED_AS · line 30

Quoted 11 lines
block/Kconfig.iosched +7 -0
@@ -21,6 +21,14 @@ config IOSCHED_AS
 	  deadline I/O scheduler, it can also be slower in some cases
 	  especially some database loads.
 
+config IOPRIO_AS_MAX
+	int "Number of valid i/o priority levels"
+	depends on IOSCHED_AS
+	default "4"
+	help
+	  This option controls number of priority levels in anticipatory
+	  I/O scheduler.

Does this need to be configurable? There are two ``natural'' choices for this value; the number of iopriorities (10), or the number of priority classes (3). Why is any intermediate value useful?

block/as-iosched.c static void as_put_io_context(struct req · line 270

Quoted 9 lines
block/as-iosched.c +5 -1
 /*
  * rb tree support functions
  */
-#define RQ_RB_ROOT(ad, rq)	(&(ad)->sort_list[rq_is_sync((rq))])
+static inline struct rb_root *rq_rb_root(struct as_data *ad,
+						struct request *rq)
+{
+	return (&(ad)->prio_q[rq_prio_level(rq)].sort_list[rq_is_sync(rq)]);
+}

This change (and the related ones below) is a separate patch which could also be applied to deadline.

block/as-iosched.c static void as_move_to_dispatch(struct a · line 1084

Quoted 12 lines
block/as-iosched.c +8 -0
@@ -996,6 +1074,31 @@ static void as_move_to_dispatch(struct a
 	ad->nr_dispatched++;
 }
 
+static unsigned int select_priority_level(struct as_data *ad)
+{
+	unsigned int i, best_ioprio = 0, ioprio, found_alt = 0;
+
+	for (ioprio = 0; ioprio < IOPRIO_AS_MAX; ioprio++) {
+		if (!as_has_request_at_priority(ad, ioprio)) {
+			continue;
+		}

Unnecessary braces.

Quoted 26 lines
block/as-iosched.c +13 -9
@@ -1022,21 +1126,32 @@ static int as_dispatch_request(struct re
 		ad->changed_batch = 0;
 		ad->new_batch = 0;
 
-		while (ad->next_rq[REQ_SYNC]) {
-			as_move_to_dispatch(ad, ad->next_rq[REQ_SYNC]);
-			dispatched++;
-		}
-		ad->last_check_fifo[REQ_SYNC] = jiffies;
-
-		while (ad->next_rq[REQ_ASYNC]) {
-			as_move_to_dispatch(ad, ad->next_rq[REQ_ASYNC]);
-			dispatched++;
+		for (ioprio = 0; ioprio < IOPRIO_AS_MAX; ioprio++) {
+			while (ad->prio_q[ioprio].next_rq[REQ_SYNC]) {
+				as_move_to_dispatch(ad,
+				    ad->prio_q[ioprio].next_rq[REQ_SYNC]);
+				dispatched++;
+			}
+			ad->last_check_fifo[REQ_SYNC] = jiffies;
+
+			while (ad->prio_q[ioprio].next_rq[REQ_ASYNC]) {
+				as_move_to_dispatch(ad,
+				    ad->prio_q[ioprio].next_rq[REQ_ASYNC]);
+				dispatched++;
+			}

block/as-iosched.c static int as_dispatch_request(struct re · line 1150

Quoted 10 lines
block/as-iosched.c +4 -1
+			ad->last_check_fifo[REQ_ASYNC] = jiffies;
 		}
-		ad->last_check_fifo[REQ_ASYNC] = jiffies;
 
 		return dispatched;
 	}
 
+	ioprio = select_priority_level(ad);
+	if (ioprio >= IOPRIO_AS_MAX)
+		return 0;

Why should this ever happen?

block/as-iosched.c static int as_dispatch_request(struct re · line 1167

Quoted 5 lines
block/as-iosched.c +1 -0
@@ -1049,14 +1164,16 @@ static int as_dispatch_request(struct re
 		|| ad->changed_batch)
 		return 0;
 
+	changed_ioprio = (ad->batch_ioprio != ioprio)?1:0;

Redundant conditional.

Quoted 12 lines
block/as-iosched.c +5 -2
@@ -1216,9 +1341,39 @@ static void as_deactivate_request(struct
 static int as_queue_empty(struct request_queue *q)
 {
 	struct as_data *ad = q->elevator->elevator_data;
+	unsigned short ioprio;
 
-	return list_empty(&ad->fifo_list[REQ_ASYNC])
-		&& list_empty(&ad->fifo_list[REQ_SYNC]);
+	for (ioprio = 0; ioprio < IOPRIO_AS_MAX; ioprio++) {
+		if (as_has_request_at_priority(ad, ioprio))
+			return 0;
+	}

block/as-iosched.c static void as_deactivate_request(struct · line 1359

Quoted 10 lines
block/as-iosched.c +10 -0
+	return 1;
+}
+
+static unsigned short as_mapped_priority(unsigned short ioprio)
+{
+	unsigned short class = IOPRIO_PRIO_CLASS(ioprio);
+	unsigned short data = IOPRIO_PRIO_DATA(ioprio);
+
+	if (class == IOPRIO_CLASS_BE)
+		return ((data < IOPRIO_AS_MAX)? ioprio:

Doesn't this mean that requests in the BE class with prio level 0 will map to the same queues as RT requests?

block/as-iosched.c static void as_deactivate_request(struct · line 1364

Quoted 5 lines
block/as-iosched.c +5 -0
+			IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE,
+						(IOPRIO_AS_MAX - 1)));
+	else if (class == IOPRIO_CLASS_LATENCY)
+		return ((data < IOPRIO_AS_MAX)?
+			IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, data):

Likewise.

block/as-iosched.c static void as_deactivate_request(struct · line 1376

Quoted 12 lines
block/as-iosched.c +12 -0
+			IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE,
+						(IOPRIO_AS_MAX - 1)));
+	else if (class == IOPRIO_CLASS_RT)
+		return IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0);
+	else if (class == IOPRIO_CLASS_IDLE)
+		return IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, (IOPRIO_AS_MAX - 1));
+	else if (class == IOPRIO_CLASS_NONE) {
+		return IOPRIO_AS_DEFAULT;
+	} else {
+		WARN_ON(1);
+		return IOPRIO_AS_DEFAULT;
+	}

It looks like you're mapping all ioprios to a prio level in the BE class, and using that internally. It would be simpler to use integers in [0, IOPRIO_AS_MAX) internally, and convert back to ``real'' ioprios where necessary; you do a lot of conversions... This would also be better expressed as a switch statement.

Quoted 10 lines
block/as-iosched.c +2 -4
@@ -1351,10 +1512,20 @@ static void *as_init_queue(struct reques
 	init_timer(&ad->antic_timer);
 	INIT_WORK(&ad->antic_work, as_work_handler);
 
-	INIT_LIST_HEAD(&ad->fifo_list[REQ_SYNC]);
-	INIT_LIST_HEAD(&ad->fifo_list[REQ_ASYNC]);
-	ad->sort_list[REQ_SYNC] = RB_ROOT;
-	ad->sort_list[REQ_ASYNC] = RB_ROOT;
+	for (i = IOPRIO_AS_MAX - 1; i >= 0; i--) {
+		INIT_LIST_HEAD(&ad->prio_q[i].fifo_list[REQ_SYNC]);

block/as-iosched.c static void *as_init_queue(struct reques · line 1526

Quoted 10 lines
block/as-iosched.c +10 -0
+		INIT_LIST_HEAD(&ad->prio_q[i].fifo_list[REQ_ASYNC]);
+		ad->prio_q[i].sort_list[REQ_SYNC] = RB_ROOT;
+		ad->prio_q[i].sort_list[REQ_ASYNC] = RB_ROOT;
+		ad->prio_q[i].serviced = 0;
+		if (i == 0)
+			ad->prio_q[i].ioprio_wt = 100;
+		else if (i == 1)
+			ad->prio_q[i].ioprio_wt = 5;
+		else
+			ad->prio_q[i].ioprio_wt = 1;

This seems a bit arbitrary, and means IOPRIO_AS_MAX > 3 is useless unless the weights are changed manually.

Thanks,
-- Aaron

#3 lore raw

Hello Aaron

Thanks for the review.

Some of my comments are inline.

It seems a lot of confusion is regarding the mapping of various priority levels. The maximum number of levels supported for best-effort is 8, so the natural choice would be 8. The number (3) you mentioned below is number of valid classes and is not number of levels within each class so it isn't even a choice.

The current value is due to our use-case. We need the scheduler to separate latency sensitive from background traffic. If there is i/o which we don't care about it can be third priority level, and one additional for anything else. Hence the default is 4. It can be changed if needed.

The addition of a new class IOPRIO_CLASS_LATENCY is due to the way we handle priority v/s cfq. Last I discussed with Jens, we thought we need a new class, but weren't sure about the name. As it stands now, if you use BE class for AS it would be effectively same. as IOPRIO_CLASS_LATENCY. I don't have a strong preference on this.

The mapping is from
RT all levels to - BE 0 (Hence all RT traffic is highest priority) BE levels remain same.
IDLE maps to BE 3 (Hence idle is lowest priority)

Again thanks for the comments.
-Naveen

2008/7/6 Aaron Carroll <[email protected]>:

Quoted 60 lines

Hi Naveen,

I have a few observations after a quick read through your patch.

Quoted 22 lines · [email protected] wrote:

Modifications to the Anticipatory I/O scheduler to add multiple priority levels. It makes use of anticipation and batching in current anticipatory scheduler to implement priorities.

- Minimizes the latency of highest priority level.
- Low priority requests wait for high priority requests.
- Higher priority request break any anticipating low priority request.
- If single priority level is used the scheduler behaves as an anticipatory scheduler. So no change for existing users.

With this change, it is possible for a latency sensitive job to coexist with background job.

Other possible use of this patch is in context of I/O subsystem controller.
It can add another dimension to the parameters controlling a particular cgroup.
While we can easily divide b/w among existing croups, setting a bound on latency is not a feasible solution. Hence in context of storage devices bandwidth and priority can be two parameters controlling I/O.

Quoted 8 lines

In this patch I have added a new class IOPRIO_CLASS_LATENCY to differentiate
notion of absolute priority over existing uses of various time-slice based priority classes in cfq. Though internally within anticipatory scheduler all
of them map to best-effort levels. Hence, one can also use various best-effort
priority levels.

I don't see the point of this new priority class; I think ``latency sensitive''
is a reasonable definition of real-time. Especially since you don't actually
use it.

block/Kconfig.iosched config IOSCHED_AS · line 29

Quoted 10 lines
block/Kconfig.iosched +6 -0
@@ -21,6 +21,14 @@ config IOSCHED_AS
         deadline I/O scheduler, it can also be slower in some cases
         especially some database loads.
 +config IOPRIO_AS_MAX
+       int "Number of valid i/o priority levels"
+       depends on IOSCHED_AS
+       default "4"
+       help
+         This option controls number of priority levels in anticipatory
+         I/O scheduler.

Does this need to be configurable? There are two ``natural'' choices for this
value; the number of iopriorities (10), or the number of priority classes (3).
Why is any intermediate value useful?

Quoted 13 lines
Quoted 10 lines
block/as-iosched.c +4 -1
 /*
 * rb tree support functions
 */
-#define RQ_RB_ROOT(ad, rq)     (&(ad)->sort_list[rq_is_sync((rq))])
+static inline struct rb_root *rq_rb_root(struct as_data *ad,
+                                               struct request *rq)
+{
+       return

(&(ad)->prio_q[rq_prio_level(rq)].sort_list[rq_is_sync(rq)]); +}

This change (and the related ones below) is a separate patch which could also be applied to deadline.

Sure, minus the priority functionality.

Quoted 14 lines

block/as-iosched.c static void as_move_to_dispatch(struct a · line 1083

Quoted 12 lines
block/as-iosched.c +7 -0
@@ -996,6 +1074,31 @@ static void as_move_to_dispatch(struct a
       ad->nr_dispatched++;
 }
 +static unsigned int select_priority_level(struct as_data *ad)
+{
+       unsigned int i, best_ioprio = 0, ioprio, found_alt = 0;
+
+       for (ioprio = 0; ioprio < IOPRIO_AS_MAX; ioprio++) {
+               if (!as_has_request_at_priority(ad, ioprio)) {
+                       continue;
+               }

Unnecessary braces.

Sure. Thanks

Quoted 29 lines
Quoted 29 lines
block/as-iosched.c +14 -8
@@ -1022,21 +1126,32 @@ static int as_dispatch_request(struct re
               ad->changed_batch = 0;
               ad->new_batch = 0;
 -               while (ad->next_rq[REQ_SYNC]) {
-                       as_move_to_dispatch(ad, ad->next_rq[REQ_SYNC]);
-                       dispatched++;
-               }
-               ad->last_check_fifo[REQ_SYNC] = jiffies;
-
-               while (ad->next_rq[REQ_ASYNC]) {
-                       as_move_to_dispatch(ad, ad->next_rq[REQ_ASYNC]);
-                       dispatched++;
+               for (ioprio = 0; ioprio < IOPRIO_AS_MAX; ioprio++) {
+                       while (ad->prio_q[ioprio].next_rq[REQ_SYNC]) {
+                               as_move_to_dispatch(ad,
+                                   ad->prio_q[ioprio].next_rq[REQ_SYNC]);
+                               dispatched++;
+                       }
+                       ad->last_check_fifo[REQ_SYNC] = jiffies;
+
+                       while (ad->prio_q[ioprio].next_rq[REQ_ASYNC]) {
+                               as_move_to_dispatch(ad,
+
 ad->prio_q[ioprio].next_rq[REQ_ASYNC]);
+                               dispatched++;
+                       }
+                       ad->last_check_fifo[REQ_ASYNC] = jiffies;
               }
Quoted 9 lines

block/as-iosched.c static int as_dispatch_request(struct re · line 1150

Quoted 6 lines
block/as-iosched.c +2 -1
-               ad->last_check_fifo[REQ_ASYNC] = jiffies;
                 return dispatched;
       }
 +       ioprio = select_priority_level(ad);
+       if (ioprio >= IOPRIO_AS_MAX)
+               return 0;

Why should this ever happen?

If there is no request pending.

Quoted 6 lines

block/as-iosched.c static int as_dispatch_request(struct re · line 1166

Quoted 4 lines
block/as-iosched.c +0 -0
@@ -1049,14 +1164,16 @@ static int as_dispatch_request(struct re
               || ad->changed_batch)
               return 0;
 +       changed_ioprio = (ad->batch_ioprio != ioprio)?1:0;

Redundant conditional.

Sure thanks.

Quoted 72 lines
Quoted 12 lines
block/as-iosched.c +5 -1
@@ -1216,9 +1341,39 @@ static void as_deactivate_request(struct
 static int as_queue_empty(struct request_queue *q)
 {
       struct as_data *ad = q->elevator->elevator_data;
+       unsigned short ioprio;
 -       return list_empty(&ad->fifo_list[REQ_ASYNC])
-               && list_empty(&ad->fifo_list[REQ_SYNC]);
+       for (ioprio = 0; ioprio < IOPRIO_AS_MAX; ioprio++) {
+               if (as_has_request_at_priority(ad, ioprio))
+                       return 0;
+       }

block/as-iosched.c static void as_deactivate_request(struct · line 1359

Quoted 10 lines
block/as-iosched.c +10 -0
+       return 1;
+}
+
+static unsigned short as_mapped_priority(unsigned short ioprio)
+{
+       unsigned short class = IOPRIO_PRIO_CLASS(ioprio);
+       unsigned short data = IOPRIO_PRIO_DATA(ioprio);
+
+       if (class == IOPRIO_CLASS_BE)
+               return ((data < IOPRIO_AS_MAX)? ioprio:

Doesn't this mean that requests in the BE class with prio level 0 will map to
the same queues as RT requests?

block/as-iosched.c static void as_deactivate_request(struct · line 1364

Quoted 5 lines
block/as-iosched.c +5 -0
+                       IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE,
+                                               (IOPRIO_AS_MAX - 1)));
+       else if (class == IOPRIO_CLASS_LATENCY)
+               return ((data < IOPRIO_AS_MAX)?
+                       IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, data):

Likewise.

block/as-iosched.c static void as_deactivate_request(struct · line 1376

Quoted 13 lines
block/as-iosched.c +12 -0
+                       IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE,
+                                               (IOPRIO_AS_MAX - 1)));
+       else if (class == IOPRIO_CLASS_RT)
+               return IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0);
+       else if (class == IOPRIO_CLASS_IDLE)
+               return IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, (IOPRIO_AS_MAX -
 1));
+       else if (class == IOPRIO_CLASS_NONE) {
+               return IOPRIO_AS_DEFAULT;
+       } else {
+               WARN_ON(1);
+               return IOPRIO_AS_DEFAULT;
+       }

It looks like you're mapping all ioprios to a prio level in the BE class, and using
that internally. It would be simpler to use integers in [0, IOPRIO_AS_MAX) internally,
and convert back to ``real'' ioprios where necessary; you do a lot of conversions...
This would also be better expressed as a switch statement.

Quoted 15 lines
block/as-iosched.c +8 -3
@@ -1351,10 +1512,20 @@ static void *as_init_queue(struct reques
       init_timer(&ad->antic_timer);
       INIT_WORK(&ad->antic_work, as_work_handler);
 -       INIT_LIST_HEAD(&ad->fifo_list[REQ_SYNC]);
-       INIT_LIST_HEAD(&ad->fifo_list[REQ_ASYNC]);
-       ad->sort_list[REQ_SYNC] = RB_ROOT;
-       ad->sort_list[REQ_ASYNC] = RB_ROOT;
+       for (i = IOPRIO_AS_MAX - 1; i >= 0; i--) {
+               INIT_LIST_HEAD(&ad->prio_q[i].fifo_list[REQ_SYNC]);
+               INIT_LIST_HEAD(&ad->prio_q[i].fifo_list[REQ_ASYNC]);
+               ad->prio_q[i].sort_list[REQ_SYNC] = RB_ROOT;
+               ad->prio_q[i].sort_list[REQ_ASYNC] = RB_ROOT;
+               ad->prio_q[i].serviced = 0;
+               if (i == 0)
+                       ad->prio_q[i].ioprio_wt = 100;
Quoted 8 lines

block/as-iosched.c static void *as_init_queue(struct reques · line 1526

Quoted 4 lines
block/as-iosched.c +4 -0
+               else if (i == 1)
+                       ad->prio_q[i].ioprio_wt = 5;
+               else
+                       ad->prio_q[i].ioprio_wt = 1;

This seems a bit arbitrary, and means IOPRIO_AS_MAX > 3 is useless unless the weights are
changed manually.

You are right. these are meant to be configurable and workload dependent.

Quoted 7 lines

Thanks,
-- Aaron