Skip to contentSkip to conversation

scheduler fix for 1cpu/node case

PATCH In review Erich Focht 7 participants 25 messages Jul 28, 2003 – Aug 23, 2003

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 scheduler fix for 1cpu/node case

Hi,

after talking to several people at OLS about the current NUMA scheduler the conclusion was:
(1) it sucks (for particular workloads),
(2) on x86_64 (embarassingly simple NUMA) it's useless, goto (1).

Fact is that the current separation of local and global balancing, where global balancing is done only in the timer interrupt at a fixed rate is way too unflexible. A CPU going idle inside a well balanced node will stay idle for a while even if there's a lot of work to do. Especially in the corner case of one CPU per node this is condemning that CPU to idleness for at least 5 ms. So x86_64 platforms (but not only those!) suffer and whish to switch off the NUMA scheduler while keeping NUMA memory management on.

The attached patch is a simple solution which
- solves the 1 CPU / node problem,
- lets other systems behave (almost) as before,
- opens the way to other optimisations like multi-level node hierarchies (by tuning the retry rate)
- simpifies the NUMA scheduler and deletes more lines of code than it adds.

The timer interrupt based global rebalancing might appear to be a simple and good idea but it takes the scheduler a lot of flexibility. In the patch the global rebalancing is done after a certain number of failed attempts to locally balance. The number of attempts is proportional to the number of CPUs in the current node. For only 1 CPU in the current node the scheduler doesn't even try to balance locally, it wouldn't make sense anyway. Of course one could instead set IDLE_NODE_REBALANCE_TICK = IDLE_REBALANCE_TICK, but this is more ugly (IMHO) and only helps when all nodes have 1 CPU / node.

Please consider this for inclusion.

Thanks,
Erich

kernel/sched.c +42 -33
diff -urNp 2.6.0t1/kernel/sched.c 2.6.0t1-1cpufix/kernel/sched.c
@@ -164,6 +164,7 @@ struct runqueue {
 	prio_array_t *active, *expired, arrays[2];
 	int prev_cpu_load[NR_CPUS];
 #ifdef CONFIG_NUMA
+	unsigned int nr_lb_failed;
 	atomic_t *node_nr_running;
 	int prev_node_load[MAX_NUMNODES];
 #endif
@@ -856,6 +857,35 @@ static int find_busiest_node(int this_no
 	return node;
 }
 
+/*
+ * Decide whether the scheduler should balance locally (inside the same node)
+ * or globally depending on the number of failed local balance attempts.
+ * The number of failed local balance attempts depends on the number of cpus
+ * in the current node. In case it's just one, go immediately for global
+ * balancing. On a busy cpu the number of retries is smaller.
+ */
+static inline unsigned long cpus_to_balance(int this_cpu, runqueue_t *this_rq)
+{
+	int node, retries, this_node = cpu_to_node(this_cpu);
+
+	retries = nr_cpus_node(this_node) - 1;
+	if (this_rq->curr != this_rq->idle)
+		retries >>= 1;
+	if (this_rq->nr_lb_failed >= retries) {
+		node = find_busiest_node(this_node);
+		this_rq->nr_lb_failed = 0;
+		if (node >= 0)
+			return (node_to_cpu_mask(node) | (1UL << this_cpu));
+	}
+	return node_to_cpu_mask(this_node);
+}
+
+#else /* !CONFIG_NUMA */
+
+static inline unsigned long cpus_to_balance(int this_cpu, runqueue_t *this_rq)
+{
+	return cpu_online_map;
+}
 #endif /* CONFIG_NUMA */
 
 #ifdef CONFIG_SMP
@@ -960,6 +990,12 @@ static inline runqueue_t *find_busiest_q
 		busiest = NULL;
 	}
 out:
+#ifdef CONFIG_NUMA
+	if (!busiest)
+		this_rq->nr_lb_failed++;
+	else
+		this_rq->nr_lb_failed = 0;
+#endif
 	return busiest;
 }
 
@@ -995,7 +1031,7 @@ static inline void pull_task(runqueue_t 
  * We call this with the current runqueue locked,
  * irqs disabled.
  */
-static void load_balance(runqueue_t *this_rq, int idle, unsigned long cpumask)
+static void load_balance(runqueue_t *this_rq, int idle)
 {
 	int imbalance, idx, this_cpu = smp_processor_id();
 	runqueue_t *busiest;
@@ -1003,7 +1039,8 @@ static void load_balance(runqueue_t *thi
 	struct list_head *head, *curr;
 	task_t *tmp;
 
-	busiest = find_busiest_queue(this_rq, this_cpu, idle, &imbalance, cpumask);
+	busiest = find_busiest_queue(this_rq, this_cpu, idle, &imbalance,
+				     cpus_to_balance(this_cpu, this_rq));
 	if (!busiest)
 		goto out;
 
@@ -1085,29 +1122,9 @@ out:
  */
 #define IDLE_REBALANCE_TICK (HZ/1000 ?: 1)
 #define BUSY_REBALANCE_TICK (HZ/5 ?: 1)
-#define IDLE_NODE_REBALANCE_TICK (IDLE_REBALANCE_TICK * 5)
-#define BUSY_NODE_REBALANCE_TICK (BUSY_REBALANCE_TICK * 2)
-
-#ifdef CONFIG_NUMA
-static void balance_node(runqueue_t *this_rq, int idle, int this_cpu)
-{
-	int node = find_busiest_node(cpu_to_node(this_cpu));
-	unsigned long cpumask, this_cpumask = 1UL << this_cpu;
-
-	if (node >= 0) {
-		cpumask = node_to_cpumask(node) | this_cpumask;
-		spin_lock(&this_rq->lock);
-		load_balance(this_rq, idle, cpumask);
-		spin_unlock(&this_rq->lock);
-	}
-}
-#endif
 
 static void rebalance_tick(runqueue_t *this_rq, int idle)
 {
-#ifdef CONFIG_NUMA
-	int this_cpu = smp_processor_id();
-#endif
 	unsigned long j = jiffies;
 
 	/*
@@ -1119,24 +1136,16 @@ static void rebalance_tick(runqueue_t *t
 	 * are not balanced.)
 	 */
 	if (idle) {
-#ifdef CONFIG_NUMA
-		if (!(j % IDLE_NODE_REBALANCE_TICK))
-			balance_node(this_rq, idle, this_cpu);
-#endif
 		if (!(j % IDLE_REBALANCE_TICK)) {
 			spin_lock(&this_rq->lock);
-			load_balance(this_rq, idle, cpu_to_node_mask(this_cpu));
+			load_balance(this_rq, idle);
 			spin_unlock(&this_rq->lock);
 		}
 		return;
 	}
-#ifdef CONFIG_NUMA
-	if (!(j % BUSY_NODE_REBALANCE_TICK))
-		balance_node(this_rq, idle, this_cpu);
-#endif
 	if (!(j % BUSY_REBALANCE_TICK)) {
 		spin_lock(&this_rq->lock);
-		load_balance(this_rq, idle, cpu_to_node_mask(this_cpu));
+		load_balance(this_rq, idle);
 		spin_unlock(&this_rq->lock);
 	}
 }
@@ -1306,7 +1315,7 @@ need_resched:
 pick_next_task:
 	if (unlikely(!rq->nr_running)) {
 #ifdef CONFIG_SMP
-		load_balance(rq, 1, cpu_to_node_mask(smp_processor_id()));
+		load_balance(rq, 1);
 		if (rq->nr_running)
 			goto pick_next_task;
 #endif
#1 lore raw
Quoted 4 lines

after talking to several people at OLS about the current NUMA scheduler the conclusion was:
(1) it sucks (for particular workloads),
(2) on x86_64 (embarassingly simple NUMA) it's useless, goto (1).

I really feel there's no point in a NUMA scheduler for the Hammer style architectures. A config option to turn it off would seem like a simpler way to go, unless people can see some advantage of the full NUMA code?

The interesting thing is probably whether we want balance on exec or not ... but that probably applies to UMA SMP as well ...

Quoted 6 lines

Fact is that the current separation of local and global balancing, where global balancing is done only in the timer interrupt at a fixed rate is way too unflexible. A CPU going idle inside a well balanced node will stay idle for a while even if there's a lot of work to do. Especially in the corner case of one CPU per node this is condemning that CPU to idleness for at least 5 ms.

Surely it'll hit the idle local balancer and rebalance within the node? Or are you thinking of a case with 3 tasks on a 4 cpu/node system?

Quoted 3 lines

So x86_64 platforms
(but not only those!) suffer and whish to switch off the NUMA scheduler while keeping NUMA memory management on.

Right - I have a patch to make it a config option (CONFIG_NUMA_SCHED) ... I'll feed that upstream this week.

Quoted 7 lines

The attached patch is a simple solution which
- solves the 1 CPU / node problem,
- lets other systems behave (almost) as before,
- opens the way to other optimisations like multi-level node hierarchies (by tuning the retry rate)
- simpifies the NUMA scheduler and deletes more lines of code than it adds.

Looks simple, I'll test it out.

Quoted 6 lines

The timer interrupt based global rebalancing might appear to be a simple and good idea but it takes the scheduler a lot of flexibility. In the patch the global rebalancing is done after a certain number of failed attempts to locally balance. The number of attempts is proportional to the number of CPUs in the current node.

Seems like a good plan.

M.

#2 lore raw
Quoted 9 lines · Martin J. Bligh
Quoted 4 lines

after talking to several people at OLS about the current NUMA scheduler the conclusion was:
(1) it sucks (for particular workloads),
(2) on x86_64 (embarassingly simple NUMA) it's useless, goto (1).

I really feel there's no point in a NUMA scheduler for the Hammer style architectures. A config option to turn it off would seem like a simpler way to go, unless people can see some advantage of the full NUMA code?

But the Hammer is a NUMA architecture and a working NUMA scheduler should be flexible enough to deal with it. And: the corner case of 1 CPU per node is possible also on any other NUMA platform, when in some of the nodes (or even just one) only one CPU is configured in. Solving that problem automatically gives the Hammer what it needs.

Quoted 9 lines
Quoted 6 lines

Fact is that the current separation of local and global balancing, where global balancing is done only in the timer interrupt at a fixed rate is way too unflexible. A CPU going idle inside a well balanced node will stay idle for a while even if there's a lot of work to do. Especially in the corner case of one CPU per node this is condemning that CPU to idleness for at least 5 ms.

Surely it'll hit the idle local balancer and rebalance within the node? Or are you thinking of a case with 3 tasks on a 4 cpu/node system?

No, no, the local load balancer just doesn't make sense now if you have one cpu per node. It is called although it will never find another cpu in the own cell to steal from. There just is nothing else... (or did I misunderstand your comment?)

Quoted 6 lines
Quoted 3 lines

So x86_64 platforms
(but not only those!) suffer and whish to switch off the NUMA scheduler while keeping NUMA memory management on.

Right - I have a patch to make it a config option (CONFIG_NUMA_SCHED) ... I'll feed that upstream this week.

That's one way, but the proposed patch just solves the problem (in a more general way, also for other NUMA cases). If you deconfigure NUMA for a NUMA platform, we'll have problems switching it back on when adding smarter things like node affine or homenode extensions.

Quoted 9 lines
Quoted 7 lines

The attached patch is a simple solution which
- solves the 1 CPU / node problem,
- lets other systems behave (almost) as before,
- opens the way to other optimisations like multi-level node hierarchies (by tuning the retry rate)
- simpifies the NUMA scheduler and deletes more lines of code than it adds.

Looks simple, I'll test it out.

Great! Thanks!

Erich

#3 lore raw
Quoted 5 lines

But the Hammer is a NUMA architecture and a working NUMA scheduler should be flexible enough to deal with it. And: the corner case of 1 CPU per node is possible also on any other NUMA platform, when in some of the nodes (or even just one) only one CPU is configured in. Solving that problem automatically gives the Hammer what it needs.

OK, well what we have now is a "multi-cpu-per-node-numa-scheduler" if you really want to say all that ;-)

The question is "does Hammer benefit from the additional complexity"? I'm guessing not ... if so, then yes, it's worth fixing. If not, it would seem better to just leave it at SMP for the scheduler stuff. Simpler, more shared code with common systems.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

Fact is that the current separation of local and global balancing, where global balancing is done only in the timer interrupt at a fixed rate is way too unflexible. A CPU going idle inside a well balanced node will stay idle for a while even if there's a lot of work to do. Especially in the corner case of one CPU per node this is condemning that CPU to idleness for at least 5 ms.

Quoted 7 lines
Quoted 2 lines

Surely it'll hit the idle local balancer and rebalance within the node? Or are you thinking of a case with 3 tasks on a 4 cpu/node system?

No, no, the local load balancer just doesn't make sense now if you have one cpu per node. It is called although it will never find another cpu in the own cell to steal from. There just is nothing else... (or did I misunderstand your comment?)

Right, I realise that the 1 cpu per node case is broken. However, doesn't this also affect the multi-cpu per node case in the manner I suggested above? So even if we turn off NUMA scheduler for Hammer, this still needs fixing, right?

Quoted 4 lines

That's one way, but the proposed patch just solves the problem (in a more general way, also for other NUMA cases). If you deconfigure NUMA for a NUMA platform, we'll have problems switching it back on when adding smarter things like node affine or homenode extensions.

Yeah, maybe. I'd rather have the code in hand that needs it, but still ... if Andi's happy that fixes it, then we're OK.

M.

#4 lore raw
Quoted 5 lines · Martin J. Bligh
Quoted 5 lines

But the Hammer is a NUMA architecture and a working NUMA scheduler should be flexible enough to deal with it. And: the corner case of 1 CPU per node is possible also on any other NUMA platform, when in some of the nodes (or even just one) only one CPU is configured in. Solving that problem automatically gives the Hammer what it needs.

I am going to ask a silly question, do we have any data showing this really is a problem on AMD? I would think, even if we have an idle cpu, sometimes a
little delay on task migration (on NUMA) may not be a bad thing. If it is
too long, can we just make the rebalance ticks arch specific?

I'd much rather have info related to the properties of the NUMA arch than something that makes decisions based on nr_cpus_node(). For example, we may want to inter-node balance as much or more often on ppc64 than even AMD, but it has 8 cpus per node. On this patch it would has the lowest inter-node balance frequency, even though it probably has one of the lowest latencies between nodes and highest throughput interconnects.

Quoted 23 lines

OK, well what we have now is a "multi-cpu-per-node-numa-scheduler" if you really want to say all that ;-)

The question is "does Hammer benefit from the additional complexity"? I'm guessing not ... if so, then yes, it's worth fixing. If not, it would seem better to just leave it at SMP for the scheduler stuff. Simpler, more shared code with common systems.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

Fact is that the current separation of local and global balancing, where global balancing is done only in the timer interrupt at a fixed rate is way too unflexible. A CPU going idle inside a well balanced node will stay idle for a while even if there's a lot of work to do. Especially in the corner case of one CPU per node this is condemning that CPU to idleness for at least 5 ms.

Quoted 7 lines
Quoted 2 lines

Surely it'll hit the idle local balancer and rebalance within the node? Or are you thinking of a case with 3 tasks on a 4 cpu/node system?

No, no, the local load balancer just doesn't make sense now if you have one cpu per node. It is called although it will never find another cpu in the own cell to steal from. There just is nothing else... (or did I misunderstand your comment?)

Quoted 4 lines

Right, I realise that the 1 cpu per node case is broken. However, doesn't this also affect the multi-cpu per node case in the manner I suggested above? So even if we turn off NUMA scheduler for Hammer, this still needs fixing, right?

Maybe so, but if we start making idle rebalance more aggressive, I think we would need to make CAN_MIGRATE more restrictive, taking memory placement of the tasks in to account. On AMD with interleaved memory allocation, tasks would move very easily, since their memory is spread out anyway. On "home node" or node-local policy, we may not move a task (or maybe not on the first attempt), even if there is an idle cpu in another node.

Quoted 7 lines
Quoted 4 lines

That's one way, but the proposed patch just solves the problem (in a more general way, also for other NUMA cases). If you deconfigure NUMA for a NUMA platform, we'll have problems switching it back on when adding smarter things like node affine or homenode extensions.

Yeah, maybe. I'd rather have the code in hand that needs it, but still ... if Andi's happy that fixes it, then we're OK.

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

-Andrew Theurer

#5 lore raw
Quoted 11 lines · Andrew Theurer
Quoted 5 lines · Martin J. Bligh
Quoted 5 lines

But the Hammer is a NUMA architecture and a working NUMA scheduler should be flexible enough to deal with it. And: the corner case of 1 CPU per node is possible also on any other NUMA platform, when in some of the nodes (or even just one) only one CPU is configured in. Solving that problem automatically gives the Hammer what it needs.

I am going to ask a silly question, do we have any data showing this really is a problem on AMD? I would think, even if we have an idle cpu, sometimes
a little delay on task migration (on NUMA) may not be a bad thing. If it
is too long, can we just make the rebalance ticks arch specific?

The fact that global rebalances are done only in the timer interrupt is simply bad! It complicates rebalance_tick() and wastes the opportunity to get feedback from the failed local balance attempts.

If you want data supporting my assumptions: Ted Ts'o's talk at OLS shows the necessity to rebalance ASAP (even in try_to_wake_up). There are plenty of arguments towards this, starting with the steal delay parameter scans from the early days of multi-queue schedulers (Davide Libenzi), over my experiments with NUMA schedulers and the observation of Andi Kleen that on Opteron you better run from the wrong CPU than wait (if the tasks returns to the right cpu, all's fine anyway).

Quoted 6 lines

I'd much rather have info related to the properties of the NUMA arch than something that makes decisions based on nr_cpus_node(). For example, we may want to inter-node balance as much or more often on ppc64 than even AMD, but it has 8 cpus per node. On this patch it would has the lowest inter-node balance frequency, even though it probably has one of the lowest latencies between nodes and highest throughput interconnects.

We can still discuss on the formula. Currently there's a bug in the scheduler and the corner case of 1 cpu/node is just broken. The function local_balance_retries(attempts, cpus_in_this_node) must return 0 for cpus_in_this_node=1 and should return larger values for larger cpus_in_this_node. To have an upper limit is fine, but maybe not necessary.

Regarding the ppc64 interconnect, I'm glad that you said "probably" and "one of the ...". No need to point you to better ones ;-)

Quoted 11 lines
Quoted 4 lines

Right, I realise that the 1 cpu per node case is broken. However, doesn't this also affect the multi-cpu per node case in the manner I suggested above? So even if we turn off NUMA scheduler for Hammer, this still needs fixing, right?

Maybe so, but if we start making idle rebalance more aggressive, I think we would need to make CAN_MIGRATE more restrictive, taking memory placement of the tasks in to account. On AMD with interleaved memory allocation, tasks would move very easily, since their memory is spread out anyway. On "home node" or node-local policy, we may not move a task (or maybe not on the first attempt), even if there is an idle cpu in another node.

Aehm, that's another story and I'm sure we will fix that too. There are a few ideas around. But you shouldn't expect to solve all problems at once, after all optimal NUMA scheduling can still be considered a research area.

Quoted 3 lines

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

:-) Then you expect that everybody who reads the scheduler code knows what NUMA stands for and what it means? Pretty optimistic, but yes, I'd like that, too.

Erich

#6 lore raw
Quoted 8 lines · Erich Focht
Quoted 7 lines · Andrew Theurer
Quoted 5 lines · Martin J. Bligh
Quoted 5 lines

But the Hammer is a NUMA architecture and a working NUMA scheduler should be flexible enough to deal with it. And: the corner case of 1 CPU per node is possible also on any other NUMA platform, when in some of the nodes (or even just one) only one CPU is configured in. Solving that problem automatically gives the Hammer what it needs.

Quoted 8 lines
Quoted 5 lines

I am going to ask a silly question, do we have any data showing this really is a problem on AMD? I would think, even if we have an idle cpu, sometimes a little delay on task migration (on NUMA) may not be a bad
thing. If it is too long, can we just make the rebalance ticks arch
specific?

The fact that global rebalances are done only in the timer interrupt is simply bad!

Even with this patch it still seems that most balances are still timer based, because we still call load_balance in rebalance_tick. Granted, we may inter-node balance more often, well, maybe less often since node_busy_rebalance_tick was busy_rebalance_tick*2. I do see the advantage of doing this at idle, but idle only, that's why I'd would be more inclined a only a much more aggressive idle rebalance.

Quoted 2 lines

It complicates rebalance_tick() and wastes the opportunity to get feedback from the failed local balance attempts.

What does "failed" really mean? To me, when *busiest=null, that means we passed, the node itself is probably balanced, and there's nothing to do. It gives no indication at all of the global load [im]balance. Shouldn't the thing we are looking for is the imbalance among node_nr_running[]? Would it make sense to go forward with a global balance based on that only?

Quoted 2 lines

If you want data supporting my assumptions: Ted Ts'o's talk at OLS shows the necessity to rebalance ASAP (even in try_to_wake_up).

If this is the patch I am thinking of, it was the (attached) one I sent them, which did a light "push" rebalance at try_to_wake_up. Calling load_balance at try_to_wake_up seems very heavy-weight. This patch only looks for an idle cpu (within the same node) to wake up on before task activation, only if the task_rq(p)->nr_running is too long. So, yes, I do believe this can be important, but I think it's only called for when we have an idle cpu.

Quoted 21 lines

There
are plenty of arguments towards this, starting with the steal delay parameter scans from the early days of multi-queue schedulers (Davide Libenzi), over my experiments with NUMA schedulers and the observation of Andi Kleen that on Opteron you better run from the wrong CPU than wait (if the tasks returns to the right cpu, all's fine anyway).

Quoted 6 lines

I'd much rather have info related to the properties of the NUMA arch than something that makes decisions based on nr_cpus_node(). For example, we may want to inter-node balance as much or more often on ppc64 than even AMD, but it has 8 cpus per node. On this patch it would has the lowest inter-node balance frequency, even though it probably has one of the lowest latencies between nodes and highest throughput interconnects.

We can still discuss on the formula. Currently there's a bug in the scheduler and the corner case of 1 cpu/node is just broken. The function local_balance_retries(attempts, cpus_in_this_node) must return 0 for cpus_in_this_node=1 and should return larger values for larger cpus_in_this_node. To have an upper limit is fine, but maybe not necessary.

Quoted 2 lines

Regarding the ppc64 interconnect, I'm glad that you said "probably" and "one of the ...". No need to point you to better ones ;-)

OK, we wont get into a pissing match :) I just wanted to base the scheduler decisions on data related to the hardware NUMA properties, not the cpu count.

Quoted 23 lines
Quoted 5 lines
Quoted 4 lines

Right, I realise that the 1 cpu per node case is broken. However, doesn't this also affect the multi-cpu per node case in the manner I suggested above? So even if we turn off NUMA scheduler for Hammer, this still needs fixing, right?

Quoted 7 lines

Maybe so, but if we start making idle rebalance more aggressive, I think we would need to make CAN_MIGRATE more restrictive, taking memory placement of the tasks in to account. On AMD with interleaved memory allocation, tasks would move very easily, since their memory is spread out anyway. On "home node" or node-local policy, we may not move a task (or maybe not on the first attempt), even if there is an idle cpu in another node.

Aehm, that's another story and I'm sure we will fix that too. There are a few ideas around. But you shouldn't expect to solve all problems at once, after all optimal NUMA scheduling can still be considered a research area.

Quoted 5 lines

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

Quoted 4 lines

:-) Then you expect that everybody who reads the scheduler code knows

what NUMA stands for and what it means? Pretty optimistic, but yes, I'd like that, too.

Yes, at some point we have to. We cannot have two different schedulers. Non numa should have the exact same scheduling policy as a numa system with one node. I don't know if that's acceptable for 2.6, but I really want to go for that in 2.7.

-Andrew Theurer

Makefile +1 -1
diff -Naur 2.5.67-BK-9-4-2003/Makefile 2.5.67-BK-9-4-2003-wakey2/Makefile
@@ -1,7 +1,7 @@
 VERSION = 2
 PATCHLEVEL = 5
 SUBLEVEL = 67
-EXTRAVERSION = -BK-9-4-2003
+EXTRAVERSION = -BK-9-4-2003-wakey2
 
 # *DOCUMENTATION*
 # To see a list of typical targets execute "make help"
kernel/sched.c +29 -26

83 lines not on this page · raw message

#7 lore raw
Quoted 9 lines · Andrew Theurer
Quoted 2 lines

The fact that global rebalances are done only in the timer interrupt is simply bad!

Even with this patch it still seems that most balances are still timer based, because we still call load_balance in rebalance_tick. Granted, we may inter-node balance more often, well, maybe less often since node_busy_rebalance_tick was busy_rebalance_tick*2. I do see the advantage of doing this at idle, but idle only, that's why I'd would be more inclined a only a much more aggressive idle rebalance.

Without this patch the probability to globally balance when going idle is 0. Now it is 1/global_balance_rate(cpus_in_this_node) . This is tunable and we can make this probability depending on the node load imbalance. I'll try that in the next version, it sounds like a good idea. Also changing this probability by some factor could give us a way to handle the differences between the platforms.

Balancing globally only when idle isn't a good idea as long as we don't have multiple steals per balance attempt. Even then, tasks don't live all the same time, so you easilly end up with one node overloaded and others just busy and not able to steal from the busiest node.

Quoted 8 lines
Quoted 2 lines

It complicates rebalance_tick() and wastes the opportunity to get feedback from the failed local balance attempts.

What does "failed" really mean? To me, when *busiest=null, that means we passed, the node itself is probably balanced, and there's nothing to do. It gives no indication at all of the global load [im]balance. Shouldn't the thing we are looking for is the imbalance among node_nr_running[]? Would it make sense to go forward with a global balance based on that only?

I'll try to include the node imbalance in the global balance rate calculation, let's see how it works. Just wanted to fix one issue with my patch, now it looks like it provides some simple ways to solve other issues as well... Thanks for the idea!

Regards,
Erich

#8 lore raw
Quoted 18 lines · Erich Focht
Quoted 9 lines · Andrew Theurer
Quoted 2 lines

The fact that global rebalances are done only in the timer interrupt is simply bad!

Even with this patch it still seems that most balances are still timer based, because we still call load_balance in rebalance_tick. Granted, we may inter-node balance more often, well, maybe less often since node_busy_rebalance_tick was busy_rebalance_tick*2. I do see the advantage of doing this at idle, but idle only, that's why I'd would be more inclined a only a much more aggressive idle rebalance.

Without this patch the probability to globally balance when going idle is 0. Now it is 1/global_balance_rate(cpus_in_this_node) . This is tunable and we can make this probability depending on the node load imbalance. I'll try that in the next version, it sounds like a good idea. Also changing this probability by some factor could give us a way to handle the differences between the platforms.

Quoted 4 lines

Balancing globally only when idle isn't a good idea as long as we don't have multiple steals per balance attempt. Even then, tasks don't live all the same time, so you easilly end up with one node overloaded and others just busy and not able to steal from the busiest node.

I think I would agree with this statements. Yes, your patch does fix the "going idle" AMD issue, and yes, we don't want to balance globally on idle by default. However I do still think there may be a better way to solve this.

I see this as "range" problem. When balancing, the NUMA scheduler's tactic is to limit the range of cpus to find a busiest queue. First we start with within a node, then once in a while we go to the next "level", and look at cpus in all nodes in that "level". Someday we may even go up another "level" and include even more cpus in our range. We limit how often we go up a level, as to help memory locality of the running tasks. This is obviously
well known to you, since you wrote most of this :) What I would like to
propose is the following:

For load_balance, each architecture tells the scheduler what the range is, where the range starts, and where it ends. For ex: Summit starts at level1, the cpus in a node, and ends at level2, cpus in all level1 nodes. AMD starts at level2, all the cpus in all level1 nodes and also ends at the level2. 8-way AMD would go to level3 :) NEC (I assume) would start at the level1, and end at level3, the "super" nodes. This should solve the AMD problem very easily.

As for idle balances, we may be able to go a step further: follow the range rules, but do a more aggressive/frequent search. On busy rebalance, I'd prefer to still uphold the node_rebalance_ticks, but on idle, I would think, since we are idle, some more time spent on a thorough search would be OK: We first start at the lower range and try to find something to steal (always favor stealing a smaller range first), then as needed increase the range, looking for a cpu to steal from. Do not use a node_idle_rebalance interval, just keep increasing the range until we are successful.

I'll see if I can get something coded up here. I don't think these concepts would be too difficult to add.

-Andrew Theurer

#9 lore raw
In reply to Erich Focht
Quoted 8 lines
Quoted 4 lines

I am going to ask a silly question, do we have any data showing this really is a problem on AMD? I would think, even if we have an idle cpu, sometimes
a little delay on task migration (on NUMA) may not be a bad thing. If it
is too long, can we just make the rebalance ticks arch specific?

The fact that global rebalances are done only in the timer interrupt is simply bad! It complicates rebalance_tick() and wastes the opportunity to get feedback from the failed local balance attempts.

The whole point of the NUMA scheduler is to rebalance globally less often. Now I'd agree that in the idle case we want to be more agressive, as your patch does, but we need to be careful not to end up in a cacheline thrashing war.

Aiming for perfect balance is not always a good idea - the expense of both the calculation and the migrations has to be taken into account. For some arches, it's less important than others ... one thing we're missing is a per arch "node-balance-agressiveness" factor.

Having said that, the NUMA scheduler is clearly broken on Hammer at the moment, from Andi's observations.

Quoted 7 lines

If you want data supporting my assumptions: Ted Ts'o's talk at OLS shows the necessity to rebalance ASAP (even in try_to_wake_up). There are plenty of arguments towards this, starting with the steal delay parameter scans from the early days of multi-queue schedulers (Davide Libenzi), over my experiments with NUMA schedulers and the observation of Andi Kleen that on Opteron you better run from the wrong CPU than wait (if the tasks returns to the right cpu, all's fine anyway).

That's a drastic oversimplification. It may be better in some circumstances, on some benchmarks. For now, let's just get your patch tested on Hammer, and see if it works better to have the NUMA scheduler on than off after your patch ...

M.

#10 lore raw
In reply to Andrew Theurer
Quoted 3 lines · Andrew Theurer

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

That sounds like a great idea, but I'm not sure it could be realized short of a major rewrite. Look how hard Ingo and Con are working just to get a single node doing a good job with interactive and throughput tradeoffs.

Once they get a good handle on identifying process behaviour, and I believe they will, that information could be used in improving NUMA performance, by sending not just 'a job" but "the right job" if it exists. I'm sure there are still a few graduate theses possible on the topic!

Signature · 3 lines
bill davidsen <[email protected]>
  CTO, TMR Associates, Inc
Doing interesting things with little computers since 1979.
#11 lore raw
Quoted 9 lines · Bill Davidsen
Quoted 4 lines · Andrew Theurer

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

That sounds like a great idea, but I'm not sure it could be realized short of a major rewrite. Look how hard Ingo and Con are working just to get a single node doing a good job with interactive and throughput tradeoffs.

Actually it's not too bad. Attached is a patch to do it. It also does multi-level node support and makes all the load balance routines runqueue-centric instead of cpu-centric, so adding something like shared runqueues (for HT) should be really easy. Hmm, other things: inter-node balance intervals are now arch specific (AMD is "1"). The default busy/idle balance timers of 200/1 are not arch specific, but I'm thinking they should be. And for non-numa, the scheduling policy is the same as it was with vanilla O(1).

Quoted 4 lines

Once they get a good handle on identifying process behaviour, and I believe they will, that information could be used in improving NUMA performance, by sending not just 'a job" but "the right job" if it exists. I'm sure there are still a few graduate theses possible on the topic!

I do agree, but I think _what_ we pick is definitely 2.7 work. All I'd like to see for 2.6 is a very nice, unified framework to build on.

As for 1cpu/node case I think this second patch (on top of first) will take care of it; however I have not had a chance to test on AMD yet.

-Andrew Theurer

kernel/sched.c +7 -4
diff -Naurp linux-2.6.0-test3-bk8-numasched/kernel/sched.c linux-2.6.0-test3-bk8-numasched2/kernel/sched.c
@@ -1261,7 +1261,7 @@ static void rebalance_tick(runqueue_t *t
 
 	if (idle) {
 		while (node) {
-			if (!(j % node->idle_rebalance_tick))
+			if (!(j % node->idle_rebalance_tick) && (node->nr_cpus > 1))
 				balance_node(this_rq, idle, last_node, node);
 			last_node = node;
 			node = node->parent_node;
@@ -1269,7 +1269,7 @@ static void rebalance_tick(runqueue_t *t
 		return;
 	}
 	while (node) {
-		if (!(j % node->busy_rebalance_tick))
+		if (!(j % node->busy_rebalance_tick) && (node->nr_cpus > 1))
 			balance_node(this_rq, idle, last_node, node);
 		last_node = node;
 		node = node->parent_node;
@@ -1405,6 +1405,7 @@ asmlinkage void schedule(void)
 	runqueue_t *rq;
 	prio_array_t *array;
 	struct list_head *queue;
+	struct node_t *node;
 	int idx;
 
 	/*
@@ -1449,8 +1450,10 @@ need_resched:
 pick_next_task:
 	if (unlikely(!rq->nr_running)) {
 #ifdef CONFIG_SMP
-		if (rq->node)
-			load_balance(rq, 1, rq->node);
+		node = rq->node;
+		while (node->nr_cpus < 2 && node != top_node)
+			node = node->parent_node;
+		load_balance(rq, 1, node);
 		if (rq->nr_running)
 			goto pick_next_task;
 #endif
arch/i386/kernel/smpboot.c +4 -0
diff -Naurp linux-2.6.0-test3-bk8/arch/i386/kernel/smpboot.c linux-2.6.0-test3-bk8-numasched/arch/i386/kernel/smpboot.c
@@ -504,12 +504,16 @@ cpumask_t node_2_cpu_mask[MAX_NR_NODES] 
 /* which node each logical CPU is on */
 int cpu_2_node[NR_CPUS] = { [0 ... NR_CPUS-1] = 0 };
 
+/* which node each node is on */
+int node_2_node[MAX_NUMNODES] = { [0 ... MAX_NUMNODES-1] = MAX_NUMNODES-1 };
+
 /* set up a mapping between cpu and node. */
 static inline void map_cpu_to_node(int cpu, int node)
 {
 	printk("Mapping cpu %d to node %d\n", cpu, node);
 	cpu_set(cpu, node_2_cpu_mask[node]);
 	cpu_2_node[cpu] = node;
+	node_2_node[node] = MAX_NUMNODES-1;
 }
 
 /* undo a mapping between cpu and node. */
arch/x86_64/kernel/smpboot.c +3 -0
diff -Naurp linux-2.6.0-test3-bk8/arch/x86_64/kernel/smpboot.c linux-2.6.0-test3-bk8-numasched/arch/x86_64/kernel/smpboot.c
@@ -63,6 +63,9 @@ static cpumask_t smp_commenced_mask;
 /* Per CPU bogomips and other parameters */
 struct cpuinfo_x86 cpu_data[NR_CPUS] __cacheline_aligned;
 
+/* which node each node is on */
+volatile int node_2_node[MAX_NUMNODES] = { [0 ... MAX_NUMNODES-1] = MAX_NUMNODES-1;
+
 /* Set when the idlers are all forked */
 int smp_threads_ready;
 
include/asm-generic/topology.h +5 -2
diff -Naurp linux-2.6.0-test3-bk8/include/asm-generic/topology.h linux-2.6.0-test3-bk8-numasched/include/asm-generic/topology.h
@@ -52,8 +52,11 @@
 #endif
 
 /* Cross-node load balancing interval. */
-#ifndef NODE_BALANCE_RATE
-#define NODE_BALANCE_RATE 10
+#ifndef IDLE_NODE_BALANCE_INTERVAL
+#define IDLE_NODE_BALANCE_INTERVAL 2
+#endif
+#ifndef BUSY_NODE_BALANCE_INTERVAL
+#define BUSY_NODE_BALANCE_INTERVAL 5
 #endif
 
 #endif /* _ASM_GENERIC_TOPOLOGY_H */
include/asm-i386/topology.h +8 -4
diff -Naurp linux-2.6.0-test3-bk8/include/asm-i386/topology.h linux-2.6.0-test3-bk8-numasched/include/asm-i386/topology.h
@@ -36,6 +36,7 @@
 /* Mappings between logical cpu number and node number */
 extern cpumask_t node_2_cpu_mask[];
 extern int cpu_2_node[];
+extern int node_2_node[];
 
 /* Returns the number of the node containing CPU 'cpu' */
 static inline int cpu_to_node(int cpu)
@@ -46,9 +47,11 @@ static inline int cpu_to_node(int cpu)
 /* Returns the number of the node containing MemBlk 'memblk' */
 #define memblk_to_node(memblk) (memblk)
 
-/* Returns the number of the node containing Node 'node'.  This architecture is flat, 
-   so it is a pretty simple function! */
-#define parent_node(node) (node)
+/* Returns the number of the node containing Node 'node'. */   
+static inline int parent_node(int node)
+{
+	return node_2_node[node];
+}
 
 /* Returns a bitmask of CPUs on Node 'node'. */
 static inline cpumask_t node_to_cpumask(int node)
@@ -73,7 +76,8 @@ static inline cpumask_t pcibus_to_cpumas
 }
 
 /* Cross-node load balancing interval. */
-#define NODE_BALANCE_RATE 100
+#define IDLE_NODE_BALANCE_INTERVAL 2
+#define BUSY_NODE_BALANCE_INTERVAL 5
 
 #else /* !CONFIG_NUMA */
 /*
include/asm-x86_64/topology.h +2 -1
diff -Naurp linux-2.6.0-test3-bk8/include/asm-x86_64/topology.h linux-2.6.0-test3-bk8-numasched/include/asm-x86_64/topology.h
@@ -27,7 +27,8 @@ static inline cpumask_t pcibus_to_cpumas
 	return ret;
 }
 
-#define NODE_BALANCE_RATE 30	/* CHECKME */ 
+#define IDLE_NODE_BALANCE_INTERVAL	1
+#define BUSY_NODE_BALANCE_INTERVAL	1
 
 #endif
 
include/linux/sched.h +3 -7
diff -Naurp linux-2.6.0-test3-bk8/include/linux/sched.h linux-2.6.0-test3-bk8-numasched/include/linux/sched.h
@@ -498,14 +498,10 @@ static inline int set_cpus_allowed(task_
 }
 #endif
 
-#ifdef CONFIG_NUMA
+extern void numa_sched_topology_report(void);
+extern void rq_to_node_init(void);
+extern void node_to_node_init(void);
 extern void sched_balance_exec(void);
-extern void node_nr_running_init(void);
-#else
-#define sched_balance_exec()   {}
-#define node_nr_running_init() {}
-#endif
-
 extern void set_user_nice(task_t *p, long nice);
 extern int task_prio(task_t *p);
 extern int task_nice(task_t *p);
init/main.c +3 -2
diff -Naurp linux-2.6.0-test3-bk8/init/main.c linux-2.6.0-test3-bk8-numasched/init/main.c
@@ -537,11 +537,12 @@ static void do_pre_smp_initcalls(void)
 {
 	extern int spawn_ksoftirqd(void);
 #ifdef CONFIG_SMP
+	node_to_node_init();
+	rq_to_node_init();
+	numa_sched_topology_report();
 	extern int migration_init(void);
-
 	migration_init();
 #endif
-	node_nr_running_init();
 	spawn_ksoftirqd();
 }
 
kernel/sched.c +262 -141

570 lines not on this page · raw message

#12 lore raw
Quoted 9 lines · Andrew Theurer
Quoted 7 lines · Bill Davidsen
Quoted 5 lines · Andrew Theurer

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

Quoted 7 lines
Quoted 4 lines

That sounds like a great idea, but I'm not sure it could be realized short of a major rewrite. Look how hard Ingo and Con are working just to get a single node doing a good job with interactive and throughput tradeoffs.

Actually it's not too bad. Attached is a patch to do it. It also does multi-level node support and makes all the load balance routines runqueue-centric instead of cpu-centric, so adding something like shared runqueues (for HT) should be really easy. Hmm, other things: inter-node balance intervals are now arch specific (AMD is "1"). The default busy/idle balance timers of 200/1 are not arch specific, but I'm thinking they should be. And for non-numa, the scheduling policy is the same as it was with vanilla O(1).

I'm not saying you're wrong, but do you have some numbers where this helps? ie. two architectures that need very different balance numbers. And what is the reason for making AMD's balance interval 1?

Also, things like nr_running_inc are supposed to be very fast. I am a bit worried to see a loop and CPU shared atomics in there.

node_2_node is an odd sounding conversion too ;)

BTW. you should be CC'ing Ingo if you have any intention of scheduler stuff getting into 2.6.

#13 lore raw
Quoted 22 lines · Nick Piggin
Quoted 12 lines · Andrew Theurer
Quoted 10 lines · Bill Davidsen
Quoted 4 lines · Andrew Theurer

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

That sounds like a great idea, but I'm not sure it could be realized short of a major rewrite. Look how hard Ingo and Con are working just to get a single node doing a good job with interactive and throughput tradeoffs.

Quoted 8 lines

Actually it's not too bad. Attached is a patch to do it. It also does multi-level node support and makes all the load balance routines runqueue-centric instead of cpu-centric, so adding something like shared runqueues (for HT) should be really easy. Hmm, other things: inter-node balance intervals are now arch specific (AMD is "1"). The default busy/idle balance timers of 200/1 are not arch specific, but I'm thinking they should be. And for non-numa, the scheduling policy is the same as it was with vanilla O(1).

Quoted 3 lines

I'm not saying you're wrong, but do you have some numbers where this helps? ie. two architectures that need very different balance numbers. And what is the reason for making AMD's balance interval 1?

AMD is 1 because there's no need to balance within a node, so I want the inter-node balance frequency to be as often as it was with just O(1). This interval would not work well with other NUMA boxes, so that's the main reason to have arch specific intervals. And, as a general guideline, boxes with different local-remote latency ratios will probably benefit from different inter-node balance intervals. I don't know what these ratios are, but I'd like the kernel to have the ability to change for one arch and not affect another.

Quoted 2 lines

Also, things like nr_running_inc are supposed to be very fast. I am a bit worried to see a loop and CPU shared atomics in there.

That has concerned me, too. So far I haven't been able to see a measurable difference either way (within noise level), but it's possible. The other alternative is to sum up node load at sched_best_cpu and find_busiest_node.

Quoted 1 line

node_2_node is an odd sounding conversion too ;)

I just went off the toplogy already there, so I left it.

Quoted 2 lines

BTW. you should be CC'ing Ingo if you have any intention of scheduler stuff getting into 2.6.

OK, thanks!

#14 lore raw
Quoted 27 lines · Andrew Theurer
Quoted 25 lines · Nick Piggin
Quoted 23 lines · Andrew Theurer
Quoted 7 lines · Bill Davidsen
Quoted 5 lines · Andrew Theurer

Personally, I'd like to see all systems use NUMA sched, non NUMA systems being a single node (no policy difference from non-numa sched), allowing us to remove all NUMA ifdefs. I think the code would be much more readable.

Quoted 5 lines

That sounds like a great idea, but I'm not sure it could be realized short of a major rewrite. Look how hard Ingo and Con are working just to get a single node doing a good job with interactive and throughput tradeoffs.

Actually it's not too bad. Attached is a patch to do it. It also does multi-level node support and makes all the load balance routines runqueue-centric instead of cpu-centric, so adding something like shared runqueues (for HT) should be really easy. Hmm, other things: inter-node balance intervals are now arch specific (AMD is "1"). The default busy/idle balance timers of 200/1 are not arch specific, but I'm thinking they should be. And for non-numa, the scheduling policy is the same as it was with vanilla O(1).

Quoted 7 lines
Quoted 4 lines

I'm not saying you're wrong, but do you have some numbers where this helps? ie. two architectures that need very different balance numbers. And what is the reason for making AMD's balance interval 1?

AMD is 1 because there's no need to balance within a node, so I want the inter-node balance frequency to be as often as it was with just O(1). This interval would not work well with other NUMA boxes, so that's the main reason to have arch specific intervals.

OK, I misread the patch. IIRC AMD has 1 CPU per node? If so, why doesn't this simply prevent balancing within a node?

Quoted 3 lines

And, as a general guideline, boxes with
different local-remote latency ratios will probably benefit from different inter-node balance intervals. I don't know what these ratios are, but I'd like the kernel to have the ability to change for one arch and not affect another.

I fully appreciate there are huge differences... I am curious if you can see much improvements in practice.

Quoted 7 lines
Quoted 4 lines

Also, things like nr_running_inc are supposed to be very fast. I am a bit worried to see a loop and CPU shared atomics in there.

That has concerned me, too. So far I haven't been able to see a measurable difference either way (within noise level), but it's possible. The other alternative is to sum up node load at sched_best_cpu and find_busiest_node.

Hmm... get someone to try the scheduler benchmarks on a 32 way box ;)

Quoted 4 lines
Quoted 3 lines

node_2_node is an odd sounding conversion too ;)

Quoted 10 lines

I just went off the toplogy already there, so I left it.

Quoted 3 lines

BTW. you should be CC'ing Ingo if you have any intention of scheduler stuff getting into 2.6.

OK, thanks!

Good luck with it. Definitely some good ideas.

#15 lore raw
Quoted 1 line · Nick Piggin

Hmm... get someone to try the scheduler benchmarks on a 32 way box ;)

What scheduler benchmark?

-- wli

#16 lore raw
Quoted 8 lines · William Lee Irwin III
Quoted 2 lines · Nick Piggin

Hmm... get someone to try the scheduler benchmarks on a 32 way box ;)

What scheduler benchmark?

I don't know! I don't care about high end scheduler performance. Volanomark? Kernbench? SDET? Whatever you care about.

#17 lore raw
In reply to Nick Piggin
Quoted 7 lines
Quoted 4 lines

AMD is 1 because there's no need to balance within a node, so I want the inter-node balance frequency to be as often as it was with just O(1). This interval would not work well with other NUMA boxes, so that's the main reason to have arch specific intervals.

OK, I misread the patch. IIRC AMD has 1 CPU per node? If so, why doesn't this simply prevent balancing within a node?

Yes, one cpu/node. Oh, it does prevent it, but with the current intervals, we end up not really balancing as often (since we need a inter-node balance), and when we call load_balance in schedule when idle, we don't balance at all since it's only a node local balance.

Quoted 8 lines
Quoted 5 lines

And, as a general guideline, boxes with
different local-remote latency ratios will probably benefit from different inter-node balance intervals. I don't know what these ratios are, but I'd like the kernel to have the ability to change for one arch and not affect another.

I fully appreciate there are huge differences... I am curious if you can see much improvements in practice.

I think AMD would be the first good test. Maybe Andi has some results on numasched vs O(1), which would be a good indication.

#18 lore raw
In reply to Andrew Theurer
Quoted 3 lines
Quoted 1 line

node_2_node is an odd sounding conversion too ;)

I just went off the toplogy already there, so I left it.

I thought we changed that to parent_node or something a while back? Yes, node_2_node is rather nondescriptive ...

Matt?

M.

#19 lore raw
In reply to Martin J. Bligh
Quoted 8 lines · Martin J. Bligh
Quoted 5 lines

But the Hammer is a NUMA architecture and a working NUMA scheduler should be flexible enough to deal with it. And: the corner case of 1 CPU per node is possible also on any other NUMA platform, when in some of the nodes (or even just one) only one CPU is configured in. Solving that problem automatically gives the Hammer what it needs.

OK, well what we have now is a "multi-cpu-per-node-numa-scheduler" if you really want to say all that ;-)

And with the fix posted it will just be called "numa-scheduler". It's a simplification, as you noticed.

Quoted 4 lines

The question is "does Hammer benefit from the additional complexity"? I'm guessing not ... if so, then yes, it's worth fixing. If not, it would seem better to just leave it at SMP for the scheduler stuff. Simpler, more shared code with common systems.

Hammer will just go for the other nodes, as it should, when the own node is free. Then you can benefit of NUMA improvements in the load calculation and later of smarter distribution across the nodes. Reverting to SMP is a quick fix but for improvements we'll have to get back to NUMA. So why not fix it now and stay with NUMA.

Quoted 9 lines
Quoted 4 lines

No, no, the local load balancer just doesn't make sense now if you have one cpu per node. It is called although it will never find another cpu in the own cell to steal from. There just is nothing else... (or did I misunderstand your comment?)

Right, I realise that the 1 cpu per node case is broken. However, doesn't this also affect the multi-cpu per node case in the manner I suggested above? So even if we turn off NUMA scheduler for Hammer, this still needs fixing, right?

Yes.

Quoted 7 lines
Quoted 4 lines

That's one way, but the proposed patch just solves the problem (in a more general way, also for other NUMA cases). If you deconfigure NUMA for a NUMA platform, we'll have problems switching it back on when adding smarter things like node affine or homenode extensions.

Yeah, maybe. I'd rather have the code in hand that needs it, but still ... if Andi's happy that fixes it, then we're OK.

http://marc.theaimsgroup.com/?l=linux-kernel&m=105854610325226&w=2
Andi mentioned in his talk to follow that path, too. We'll have to experiment a while with the ideas we currently have to get the initial load balancing/ dynamic homenode issue solved, there's no "golden way" right now. I'll post the stuff I mentioned in my talk soon, but want to gain some experience with it, first.

Regards,
Erich

#20 lore raw
In reply to Erich Focht
Quoted 11 lines · Erich Focht
Quoted 6 lines
Quoted 3 lines

So x86_64 platforms
(but not only those!) suffer and whish to switch off the NUMA scheduler while keeping NUMA memory management on.

Right - I have a patch to make it a config option (CONFIG_NUMA_SCHED) ... I'll feed that upstream this week.

That's one way, but the proposed patch just solves the problem (in a more general way, also for other NUMA cases). If you deconfigure NUMA for a NUMA platform, we'll have problems switching it back on when adding smarter things like node affine or homenode extensions.

That's one important point IMHO. Currently Opteron does not really need the NUMA scheduler, but it will be in future with such extensions. This means it would be better if the current scheduler supports it already so that it can be easily extended.

Thanks for the patch.

-Andi

#22 lore raw
Quoted 2 lines · Martin J. Bligh

you're using node_to_cpu_mask for ia64 ... others were using node_to_cpumask (1 less "_"), so this doesn't build ...

Ooops, you're right, of course. Sorry about this mistake :-(

Erich

kernel/sched.c +42 -33
diff -urNp 2.6.0t1/kernel/sched.c 2.6.0t1-1cpufix/kernel/sched.c
@@ -164,6 +164,7 @@ struct runqueue {
 	prio_array_t *active, *expired, arrays[2];
 	int prev_cpu_load[NR_CPUS];
 #ifdef CONFIG_NUMA
+	unsigned int nr_lb_failed;
 	atomic_t *node_nr_running;
 	int prev_node_load[MAX_NUMNODES];
 #endif
@@ -856,6 +857,35 @@ static int find_busiest_node(int this_no
 	return node;
 }
 
+/*
+ * Decide whether the scheduler should balance locally (inside the same node)
+ * or globally depending on the number of failed local balance attempts.
+ * The number of failed local balance attempts depends on the number of cpus
+ * in the current node. In case it's just one, go immediately for global
+ * balancing. On a busy cpu the number of retries is smaller.
+ */
+static inline unsigned long cpus_to_balance(int this_cpu, runqueue_t *this_rq)
+{
+	int node, retries, this_node = cpu_to_node(this_cpu);
+
+	retries = nr_cpus_node(this_node) - 1;
+	if (this_rq->curr != this_rq->idle)
+		retries >>= 1;
+	if (this_rq->nr_lb_failed >= retries) {
+		node = find_busiest_node(this_node);
+		this_rq->nr_lb_failed = 0;
+		if (node >= 0)
+			return (node_to_cpumask(node) | (1UL << this_cpu));
+	}
+	return node_to_cpumask(this_node);
+}
+
+#else /* !CONFIG_NUMA */
+
+static inline unsigned long cpus_to_balance(int this_cpu, runqueue_t *this_rq)
+{
+	return cpu_online_map;
+}
 #endif /* CONFIG_NUMA */
 
 #ifdef CONFIG_SMP
@@ -960,6 +990,12 @@ static inline runqueue_t *find_busiest_q
 		busiest = NULL;
 	}
 out:
+#ifdef CONFIG_NUMA
+	if (!busiest)
+		this_rq->nr_lb_failed++;
+	else
+		this_rq->nr_lb_failed = 0;
+#endif
 	return busiest;
 }
 
@@ -995,7 +1031,7 @@ static inline void pull_task(runqueue_t 
  * We call this with the current runqueue locked,
  * irqs disabled.
  */
-static void load_balance(runqueue_t *this_rq, int idle, unsigned long cpumask)
+static void load_balance(runqueue_t *this_rq, int idle)
 {
 	int imbalance, idx, this_cpu = smp_processor_id();
 	runqueue_t *busiest;
@@ -1003,7 +1039,8 @@ static void load_balance(runqueue_t *thi
 	struct list_head *head, *curr;
 	task_t *tmp;
 
-	busiest = find_busiest_queue(this_rq, this_cpu, idle, &imbalance, cpumask);
+	busiest = find_busiest_queue(this_rq, this_cpu, idle, &imbalance,
+				     cpus_to_balance(this_cpu, this_rq));
 	if (!busiest)
 		goto out;
 
@@ -1085,29 +1122,9 @@ out:
  */
 #define IDLE_REBALANCE_TICK (HZ/1000 ?: 1)
 #define BUSY_REBALANCE_TICK (HZ/5 ?: 1)
-#define IDLE_NODE_REBALANCE_TICK (IDLE_REBALANCE_TICK * 5)
-#define BUSY_NODE_REBALANCE_TICK (BUSY_REBALANCE_TICK * 2)
-
-#ifdef CONFIG_NUMA
-static void balance_node(runqueue_t *this_rq, int idle, int this_cpu)
-{
-	int node = find_busiest_node(cpu_to_node(this_cpu));
-	unsigned long cpumask, this_cpumask = 1UL << this_cpu;
-
-	if (node >= 0) {
-		cpumask = node_to_cpumask(node) | this_cpumask;
-		spin_lock(&this_rq->lock);
-		load_balance(this_rq, idle, cpumask);
-		spin_unlock(&this_rq->lock);
-	}
-}
-#endif
 
 static void rebalance_tick(runqueue_t *this_rq, int idle)
 {
-#ifdef CONFIG_NUMA
-	int this_cpu = smp_processor_id();
-#endif
 	unsigned long j = jiffies;
 
 	/*
@@ -1119,24 +1136,16 @@ static void rebalance_tick(runqueue_t *t
 	 * are not balanced.)
 	 */
 	if (idle) {
-#ifdef CONFIG_NUMA
-		if (!(j % IDLE_NODE_REBALANCE_TICK))
-			balance_node(this_rq, idle, this_cpu);
-#endif
 		if (!(j % IDLE_REBALANCE_TICK)) {
 			spin_lock(&this_rq->lock);
-			load_balance(this_rq, idle, cpu_to_node_mask(this_cpu));
+			load_balance(this_rq, idle);
 			spin_unlock(&this_rq->lock);
 		}
 		return;
 	}
-#ifdef CONFIG_NUMA
-	if (!(j % BUSY_NODE_REBALANCE_TICK))
-		balance_node(this_rq, idle, this_cpu);
-#endif
 	if (!(j % BUSY_REBALANCE_TICK)) {
 		spin_lock(&this_rq->lock);
-		load_balance(this_rq, idle, cpu_to_node_mask(this_cpu));
+		load_balance(this_rq, idle);
 		spin_unlock(&this_rq->lock);
 	}
 }
@@ -1306,7 +1315,7 @@ need_resched:
 pick_next_task:
 	if (unlikely(!rq->nr_running)) {
 #ifdef CONFIG_SMP
-		load_balance(rq, 1, cpu_to_node_mask(smp_processor_id()));
+		load_balance(rq, 1);
 		if (rq->nr_running)
 			goto pick_next_task;
 #endif
#23 lore raw
Quoted 5 lines · Martin J. Bligh
Quoted 2 lines

you're using node_to_cpu_mask for ia64 ... others were using node_to_cpumask (1 less "_"), so this doesn't build ...

Ooops, you're right, of course. Sorry about this mistake :-(

np - was easy to fix up ;-) I did run some benchmarks on it ... low end SDET seemed highly variable, but otherwise looked OK. If I have only 4 tasks running on a 16x (4x4), what's the rate limit on the idle cpus trying to steal now?

M.

#24 lore raw
Quoted 4 lines · Martin J. Bligh

np - was easy to fix up ;-) I did run some benchmarks on it ... low end SDET seemed highly variable, but otherwise looked OK. If I have only 4 tasks running on a 16x (4x4), what's the rate limit on the idle cpus trying to steal now?

the rate is 3ms, that's a bit too fast, maybe. And the busy rate is 200ms (instead of 400). I made some experiments with 2 CPUs per node and 1 ms seems to be definitely too fast. I'll tune the formula a bit...

Regards,
Erich