Skip to contentSkip to conversation

workqueue: use enum value to set array size of pools in gcwq

PATCH Superseded Joonsoo Kim 2 participants 19 messages Aug 13, 2012 – Aug 14, 2012 Workqueue

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/5 workqueue: use enum value to set array size of pools in gcwq

Commit 3270476a6c0ce322354df8679652f060d66526dc ('workqueue: reimplement WQ_HIGHPRI using a separate worker_pool') introduce separate worker_pool for HIGHPRI. Although there is NR_WORKER_POOLS enum value which represent size of pools, definition of worker_pool in gcwq doesn't use it. Using it makes code robust and prevent future mistakes.
So change code to use this enum value.

kernel/workqueue.c +2 -1
index 692d976..188eef8 100644
@@ -183,7 +183,8 @@ struct global_cwq {
 	struct hlist_head	busy_hash[BUSY_WORKER_HASH_SIZE];
 						/* L: hash of busy workers */
 
-	struct worker_pool	pools[2];	/* normal and highpri pools */
+	struct worker_pool	pools[NR_WORKER_POOLS];
+						/* normal and highpri pools */
 
 	wait_queue_head_t	rebind_hold;	/* rebind hold wait */
 } ____cacheline_aligned_in_smp;
Signature · 1 line
1.7.9.5
#1 lore raw

PATCH 2/5 workqueue: change value of lcpu in queue_delayed_work_on()

We assign cpu id into work struct in queue_delayed_work_on(). In current implementation, when work is come in first time, current running cpu id is assigned.
If we do queue_delayed_work_on() with CPU A on CPU B, __queue_work() invoked in delayed_work_timer_fn() go into sub-optimal path in case of WQ_NON_REENTRANT.
Change it to cpu argument is prevent to go into sub-optimal path.

kernel/workqueue.c +2 -0
index 188eef8..bc5c5e1 100644
@@ -1158,6 +1158,8 @@ int queue_delayed_work_on(int cpu, struct workqueue_struct *wq,
 
 			if (gcwq && gcwq->cpu != WORK_CPU_UNBOUND)
 				lcpu = gcwq->cpu;
+			else if (cpu >= 0)
+				lcpu = cpu;
 			else
 				lcpu = raw_smp_processor_id();
 		} else
Signature · 1 line
1.7.9.5
maintainer
#2 lore raw

Re: [PATCH 2/5] workqueue: change value of lcpu in queue_delayed_work_on()

Hello,

Quoted 7 lines · Joonsoo Kim

We assign cpu id into work struct in queue_delayed_work_on(). In current implementation, when work is come in first time, current running cpu id is assigned.
If we do queue_delayed_work_on() with CPU A on CPU B, __queue_work() invoked in delayed_work_timer_fn() go into sub-optimal path in case of WQ_NON_REENTRANT.
Change it to cpu argument is prevent to go into sub-optimal path.

Which part is suboptimal? Also, what if @cpu is WQ_UNBOUND?

Thanks.

Signature · 1 line
tejun
#3 lore raw

Re: [PATCH 2/5] workqueue: change value of lcpu in queue_delayed_work_on()

2012/8/14 Tejun Heo <[email protected]>:

Quoted 11 lines

Hello,

Quoted 7 lines · Joonsoo Kim

We assign cpu id into work struct in queue_delayed_work_on(). In current implementation, when work is come in first time, current running cpu id is assigned.
If we do queue_delayed_work_on() with CPU A on CPU B, __queue_work() invoked in delayed_work_timer_fn() go into sub-optimal path in case of WQ_NON_REENTRANT.
Change it to cpu argument is prevent to go into sub-optimal path.

Quoted 1 line

Which part is suboptimal? Also, what if @cpu is WQ_UNBOUND?

Hi.
I think a following scenario.

wq = WQ_NON_REENTRANT.
queue_delayed_work_on(CPU B) is invoked in CPU A, so lcpu = CPU A, cpu = CPU B.

In this case, we call add_time_on(CPU B), then delayed_work_timer_fn() is invoked on CPU B.
delayed_work_timer_fn() calls __queue_work(), then following comparisons return true!

                gcwq = get_gcwq(cpu);
                if (wq->flags & WQ_NON_REENTRANT &&
                    (last_gcwq = get_work_gcwq(work)) && last_gcwq != gcwq) {

I thinks that if we assign cpu to lcpu, above comparisons return false, so save some overheads.
Is there any missing part?

And, do u mean @cpu is WORK_CPU_UNBOUND?

Thanks.

maintainer
#4 lore raw

Re: [PATCH 2/5] workqueue: change value of lcpu in queue_delayed_work_on()

Hello,

Quoted 8 lines · JoonSoo Kim

wq = WQ_NON_REENTRANT.
queue_delayed_work_on(CPU B) is invoked in CPU A, so lcpu = CPU A, cpu = CPU B.

In this case, we call add_time_on(CPU B), then delayed_work_timer_fn() is invoked on CPU B.
delayed_work_timer_fn() calls __queue_work(), then following comparisons return true!

Quoted 7 lines
                gcwq = get_gcwq(cpu);
                if (wq->flags & WQ_NON_REENTRANT &&
                    (last_gcwq = get_work_gcwq(work)) && last_gcwq != gcwq) {

I thinks that if we assign cpu to lcpu, above comparisons return false, so save some overheads.
Is there any missing part?

Nope, that sound correct to me.

Quoted 1 line

And, do u mean @cpu is WORK_CPU_UNBOUND?

@cpu could be WORK_CPU_UNBOUND at that point. The timer will be added to local CPU but @work->data would be pointing to WORK_CPU_UNBOUND, again triggering the condition. Given that @cpu being WORK_CPU_UNBOUND is far more common than an actual CPU number, the patch would actually increase spurious nrt lookups. The right thing to do is probably setting cpu to raw_smp_processor_id() beforehand.

Thanks.

Signature · 1 line
tejun
#5 lore raw

Re: [PATCH 2/5] workqueue: change value of lcpu in queue_delayed_work_on()

Quoted 8 lines
Quoted 1 line

And, do u mean @cpu is WORK_CPU_UNBOUND?

@cpu could be WORK_CPU_UNBOUND at that point. The timer will be added to local CPU but @work->data would be pointing to WORK_CPU_UNBOUND, again triggering the condition. Given that @cpu being WORK_CPU_UNBOUND is far more common than an actual CPU number, the patch would actually increase spurious nrt lookups. The right thing to do is probably setting cpu to raw_smp_processor_id() beforehand.

I got your point.
Thanks for kind illustration.

Following is a alternative implementation for this.
I thinks this is too rare case, so it doesn't help in any real workload. But how do you thinks?

@@ -1156,7 +1156,9 @@ int queue_delayed_work_on(int cpu, struct workqueue_struct *wq,

                if (!(wq->flags & WQ_UNBOUND)) {
                        struct global_cwq *gcwq = get_work_gcwq(work);

- if (gcwq && gcwq->cpu != WORK_CPU_UNBOUND)
+ if (!gcwq)
+ lcpu = cpu;
+ else if (gcwq->cpu != WORK_CPU_UNBOUND)

                                lcpu = gcwq->cpu;
                        else
                                lcpu = raw_smp_processor_id();
maintainer
#6 lore raw

Re: [PATCH 2/5] workqueue: change value of lcpu in queue_delayed_work_on()

Hello,

Quoted 27 lines · JoonSoo Kim
Quoted 8 lines
Quoted 1 line

And, do u mean @cpu is WORK_CPU_UNBOUND?

@cpu could be WORK_CPU_UNBOUND at that point. The timer will be added to local CPU but @work->data would be pointing to WORK_CPU_UNBOUND, again triggering the condition. Given that @cpu being WORK_CPU_UNBOUND is far more common than an actual CPU number, the patch would actually increase spurious nrt lookups. The right thing to do is probably setting cpu to raw_smp_processor_id() beforehand.

I got your point.
Thanks for kind illustration.

Following is a alternative implementation for this.
I thinks this is too rare case, so it doesn't help in any real workload. But how do you thinks?

(quoted diff) +3 -1
@@ -1156,7 +1156,9 @@ int queue_delayed_work_on(int cpu, struct
 workqueue_struct *wq,
                if (!(wq->flags & WQ_UNBOUND)) {
                        struct global_cwq *gcwq = get_work_gcwq(work);
 
-                       if (gcwq && gcwq->cpu != WORK_CPU_UNBOUND)
+                       if (!gcwq)
+                               lcpu = cpu;
+                       else if (gcwq->cpu != WORK_CPU_UNBOUND)
                                lcpu = gcwq->cpu;
                        else
Quoted 1 line
                                lcpu = raw_smp_processor_id();

Why not just do

	if (cpu == WORK_CPU_UNBOUND)
		cpu = raw_smp_processor_id();

	if (!(wq->flags...) {
		...
		if (gcwq && gcwq->cpu != WORK_CPU_UNBOUND)
			lcpu = gcwq->cpu;
		else
			lcpu = cpu;
	}
	...

	add_timer_on(timer, cpu);

Also, can you please base the patches on top of the following git branch?

git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-3.7

Thanks.

Signature · 1 line
tejun
#7 lore raw

Re: [PATCH 2/5] workqueue: change value of lcpu in queue_delayed_work_on()

Quoted 15 lines

Why not just do

        if (cpu == WORK_CPU_UNBOUND)
                cpu = raw_smp_processor_id();

        if (!(wq->flags...) {
                ...
                if (gcwq && gcwq->cpu != WORK_CPU_UNBOUND)
                        lcpu = gcwq->cpu;
                else
                        lcpu = cpu;
        }
        ...

        add_timer_on(timer, cpu);

I re-look at code deeply and find that dwork->cpu is related to tracing information.
If we do above change, tracing information is bias.

Quoted 4 lines

Also, can you please base the patches on top of the following git branch?

git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq.git for-3.7

Sure!

#8 lore raw

PATCH 3/5 workqueue: introduce system_highpri_wq

In reply to Joonsoo Kim

Commit 3270476a6c0ce322354df8679652f060d66526dc ('workqueue: reimplement WQ_HIGHPRI using a separate worker_pool') introduce separate worker pool for HIGHPRI. When we handle busyworkers for gcwq, it can be normal worker or highpri worker. But, we don't consider this difference in rebind_workers(), we use just system_wq for highpri worker. It makes mismatch between cwq->pool and worker->pool.

It doesn't make error in current implementation, but possible in the future. Now, we introduce system_highpri_wq to use proper cwq for highpri workers in rebind_workers(). Following patch fix this issue properly.

kernel/workqueue.c +5 -2
index bc5c5e1..f69f094 100644
@@ -269,12 +269,14 @@ struct workqueue_struct {
 };
 
 struct workqueue_struct *system_wq __read_mostly;
+struct workqueue_struct *system_highpri_wq __read_mostly;
 struct workqueue_struct *system_long_wq __read_mostly;
 struct workqueue_struct *system_nrt_wq __read_mostly;
 struct workqueue_struct *system_unbound_wq __read_mostly;
 struct workqueue_struct *system_freezable_wq __read_mostly;
 struct workqueue_struct *system_nrt_freezable_wq __read_mostly;
 EXPORT_SYMBOL_GPL(system_wq);
+EXPORT_SYMBOL_GPL(system_highpri_wq);
 EXPORT_SYMBOL_GPL(system_long_wq);
 EXPORT_SYMBOL_GPL(system_nrt_wq);
 EXPORT_SYMBOL_GPL(system_unbound_wq);
@@ -3749,6 +3751,7 @@ static int __init init_workqueues(void)
 	}
 
 	system_wq = alloc_workqueue("events", 0, 0);
+	system_highpri_wq = alloc_workqueue("events_highpri", 0, 0);
 	system_long_wq = alloc_workqueue("events_long", 0, 0);
 	system_nrt_wq = alloc_workqueue("events_nrt", WQ_NON_REENTRANT, 0);
 	system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
@@ -3757,8 +3760,8 @@ static int __init init_workqueues(void)
 					      WQ_FREEZABLE, 0);
 	system_nrt_freezable_wq = alloc_workqueue("events_nrt_freezable",
 			WQ_NON_REENTRANT | WQ_FREEZABLE, 0);
-	BUG_ON(!system_wq || !system_long_wq || !system_nrt_wq ||
-	       !system_unbound_wq || !system_freezable_wq ||
+	BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
+		!system_nrt_wq || !system_unbound_wq || !system_freezable_wq ||
 		!system_nrt_freezable_wq);
 	return 0;
 }
Signature · 1 line
1.7.9.5
#9 lore raw

PATCH 4/5 workqueue: use system_highpri_wq for highpri workers in rebind_workers()

In reply to Joonsoo Kim

In rebind_workers(), we do inserting a work to rebind to cpu for busy workers. Currently, in this case, we use only system_wq. This makes a possible error situation as there is mismatch between cwq->pool and worker->pool.

To prevent this, we should use system_highpri_wq for highpri worker to match theses. This implements it.

kernel/workqueue.c +9 -3
index f69f094..e0e1d41 100644
@@ -489,6 +489,11 @@ static int worker_pool_pri(struct worker_pool *pool)
 	return pool - pool->gcwq->pools;
 }
 
+static struct workqueue_struct *get_pool_system_wq(struct worker_pool *pool)
+{
+	return worker_pool_pri(pool) ? system_highpri_wq : system_wq;
+}
+
 static struct global_cwq *get_gcwq(unsigned int cpu)
 {
 	if (cpu != WORK_CPU_UNBOUND)
@@ -1450,9 +1455,10 @@ retry:
 
 		/* wq doesn't matter, use the default one */
 		debug_work_activate(rebind_work);
-		insert_work(get_cwq(gcwq->cpu, system_wq), rebind_work,
-			    worker->scheduled.next,
-			    work_color_to_flags(WORK_NO_COLOR));
+		insert_work(
+			get_cwq(gcwq->cpu, get_pool_system_wq(worker->pool)),
+			rebind_work, worker->scheduled.next,
+			work_color_to_flags(WORK_NO_COLOR));
 	}
 }
 
Signature · 1 line
1.7.9.5
maintainer
#10 lore raw

Re: [PATCH 4/5] workqueue: use system_highpri_wq for highpri workers in rebind_workers()

Quoted 26 lines · Joonsoo Kim

In rebind_workers(), we do inserting a work to rebind to cpu for busy workers. Currently, in this case, we use only system_wq. This makes a possible error situation as there is mismatch between cwq->pool and worker->pool.

To prevent this, we should use system_highpri_wq for highpri worker to match theses. This implements it.

kernel/workqueue.c +5 -0
index f69f094..e0e1d41 100644
@@ -489,6 +489,11 @@ static int worker_pool_pri(struct worker_pool *pool)
 	return pool - pool->gcwq->pools;
 }
 
+static struct workqueue_struct *get_pool_system_wq(struct worker_pool *pool)
+{
+	return worker_pool_pri(pool) ? system_highpri_wq : system_wq;
+}
+
 static struct global_cwq *get_gcwq(unsigned int cpu)
 {
 	if (cpu != WORK_CPU_UNBOUND)

kernel/workqueue.c retry: · line 1461

Quoted 10 lines
kernel/workqueue.c +4 -3
@@ -1450,9 +1455,10 @@ retry:
 
 		/* wq doesn't matter, use the default one */
 		debug_work_activate(rebind_work);
-		insert_work(get_cwq(gcwq->cpu, system_wq), rebind_work,
-			    worker->scheduled.next,
-			    work_color_to_flags(WORK_NO_COLOR));
+		insert_work(
+			get_cwq(gcwq->cpu, get_pool_system_wq(worker->pool)),
+			rebind_work, worker->scheduled.next,
+			work_color_to_flags(WORK_NO_COLOR));

I think it would be better to just opencode system_wq selection in rebind_workers().

Thanks.

Signature · 1 line
tejun
#11 lore raw

Re: [PATCH 4/5] workqueue: use system_highpri_wq for highpri workers in rebind_workers()

Quoted 2 lines

I think it would be better to just opencode system_wq selection in rebind_workers().

Sorry for my poor English skill.
Could you elaborate "opencode system_wq selection" means?

Thanks!

maintainer
#12 lore raw

Re: [PATCH 4/5] workqueue: use system_highpri_wq for highpri workers in rebind_workers()

Quoted 5 lines · JoonSoo Kim
Quoted 2 lines

I think it would be better to just opencode system_wq selection in rebind_workers().

Sorry for my poor English skill.
Could you elaborate "opencode system_wq selection" means?

Dropping get_pool_system_wq() and putting the logic inside rebind_workers() directly. I tend to dislike short helpers which are used only once. They tend to obfuscate while not really contributing to anything else.

Thanks.

Signature · 1 line
tejun
#13 lore raw

Re: [PATCH 4/5] workqueue: use system_highpri_wq for highpri workers in rebind_workers()

2012/8/14 Tejun Heo <[email protected]>:

Quoted 11 lines · JoonSoo Kim
Quoted 5 lines
Quoted 2 lines

I think it would be better to just opencode system_wq selection in rebind_workers().

Sorry for my poor English skill.
Could you elaborate "opencode system_wq selection" means?

Dropping get_pool_system_wq() and putting the logic inside rebind_workers() directly. I tend to dislike short helpers which are used only once. They tend to obfuscate while not really contributing to anything else.

Okay!
I will send a next version which adopt your recommendation tomorrow.

#14 lore raw

PATCH 5/5 workqueue: use system_highpri_wq for unbind_work

In reply to Joonsoo Kim

To speed cpu down processing up, use system_highpri_wq. As scheduling priority of workers on it is higher than system_wq and it is not contended by other normal works on this cpu, work on it is processed faster than system_wq.

kernel/workqueue.c +1 -1
index e0e1d41..5d50955 100644
@@ -3501,7 +3501,7 @@ static int __devinit workqueue_cpu_down_callback(struct notifier_block *nfb,
 	case CPU_DOWN_PREPARE:
 		/* unbinding should happen on the local CPU */
 		INIT_WORK_ONSTACK(&unbind_work, gcwq_unbind_fn);
-		schedule_work_on(cpu, &unbind_work);
+		queue_work_on(cpu, system_highpri_wq, &unbind_work);
 		flush_work(&unbind_work);
 		break;
 	}
Signature · 1 line
1.7.9.5
maintainer
#15 lore raw

Re: [PATCH 5/5] workqueue: use system_highpri_wq for unbind_work

Quoted 4 lines · Joonsoo Kim

To speed cpu down processing up, use system_highpri_wq. As scheduling priority of workers on it is higher than system_wq and it is not contended by other normal works on this cpu, work on it is processed faster than system_wq.

Is this from an actual workload? ie. do you have a test case where this matters?

Thanks.

Signature · 1 line
tejun
#16 lore raw

Re: [PATCH 5/5] workqueue: use system_highpri_wq for unbind_work

2012/8/14 Tejun Heo <[email protected]>:

Quoted 8 lines · Joonsoo Kim
Quoted 4 lines

To speed cpu down processing up, use system_highpri_wq. As scheduling priority of workers on it is higher than system_wq and it is not contended by other normal works on this cpu, work on it is processed faster than system_wq.

Is this from an actual workload? ie. do you have a test case where this matters?

No. it is not from an actual workload.
I got this idea suddenly, during implementing others. I will do a test for this tomorrow.

maintainer
#17 lore raw

Re: [PATCH 5/5] workqueue: use system_highpri_wq for unbind_work

Hello,

Quoted 8 lines · JoonSoo Kim

2012/8/14 Tejun Heo <[email protected]>:

Quoted 3 lines · Joonsoo Kim

Is this from an actual workload? ie. do you have a test case where this matters?

No. it is not from an actual workload.
I got this idea suddenly, during implementing others. I will do a test for this tomorrow.

I don't think it's a bad idea and guess this wouldn't make much difference under most workload. Was mostly wondering whether this came from some crazy phone powersave optimization.

Thanks.

Signature · 1 line
tejun
#18 lore raw

Re: [PATCH 5/5] workqueue: use system_highpri_wq for unbind_work

2012/8/14 Tejun Heo <[email protected]>:

Quoted 8 lines

Hello,

Quoted 5 lines · JoonSoo Kim

2012/8/14 Tejun Heo <[email protected]>:

Quoted 3 lines · Joonsoo Kim

Is this from an actual workload? ie. do you have a test case where this matters?

Quoted 7 lines
Quoted 3 lines

No. it is not from an actual workload.
I got this idea suddenly, during implementing others. I will do a test for this tomorrow.

I don't think it's a bad idea and guess this wouldn't make much difference under most workload. Was mostly wondering whether this came from some crazy phone powersave optimization.

Okay.
It is not from an actual workload,
but I intent to optimize some crazy phone powersave :)

Thanks for comments.