task_work: avoid unneeded cmpxchg() in task_work_run()
PATCH 1/1 task_work: avoid unneeded cmpxchg() in task_work_run()
We only require cmpxchg()&retry when task is exiting. xchg() is enough in other cases like original code in ac3d0da8.
So we try our best to use xchg() and avoid competition&latency from task_work_add().
Also remove the inner loop
---
kernel/task_work.c +6 -7
@@ -56,14 +56,13 @@ void task_work_run(void)* work->func() can do task_work_add(), do not set * work_exited unless the list is empty. */ -do { - work =ACCESS_ONCE (task->task_works); - head = !work && (task->flags &PF_EXITING ) ? - &work_exited :NULL ; - }while (cmpxchg (&task->task_works, work, head) != work); - -if (!work) +if (!ACCESS_ONCE (task->task_works) || + !(work =xchg (&task->task_works,NULL ))) { +if ((task->flags &PF_EXITING ) && +cmpxchg (&task->task_works,NULL , &work_exited)) +continue ;break ; + }/* * Synchronize with task_work_cancel(). It can't remove * the first entry == work, cmpxchg(task_works) should
Quoted 3 lines · Lai Jiangshan
We only require cmpxchg()&retry when task is exiting.
xchg()is enough in other cases like original code inac3d0da8.
Yes, we can probably do xchg/cmpxchg depending on NULL/work_exited.
Not sure it makes sense to complicate the code though. Is xchg() really faster than cmpxchg?
Quoted 1 line
Also remove the inner loop
Yes, it is not really needed, only for readability.
"do while (!cmpxchg)" can be replaced with "if (!cmpxchg) continue".
Quoted 4 lines
kernel/task_work.c +0
-0@@ -56,14 +56,13 @@ void task_work_run(void)* work->func() can do task_work_add(), do not set
kernel/task_work.c void task_work_run(void) · line 59
Quoted 10 lines
kernel/task_work.c +1
-7* work_exited unless the list is empty. */ -do { - work =ACCESS_ONCE (task->task_works); - head = !work && (task->flags &PF_EXITING ) ? - &work_exited :NULL ; - }while (cmpxchg (&task->task_works, work, head) != work); - -if (!work) + if (!ACCESS_ONCE(task->task_works) ||
ACCESS_ONCE() looks confusing. It is not needed with this patch.
kernel/task_work.c void task_work_run(void) · line 65
Quoted 6 lines
kernel/task_work.c +5
-0+ !(work = xchg(&task->task_works, NULL))) { +if ((task->flags &PF_EXITING ) && +cmpxchg (&task->task_works,NULL , &work_exited)) +continue ;break ; + }
I think the patch is correct.
But the code looks more complex, and the only advantage is that non-exiting task does xchg() instead of cmpxchg(). Not sure this worth the trouble, in this case task_work_run() will likey run the callbacks (the caller checks ->task_works != NULL), I do not think this can add any noticeable speedup.
But, as for correctness,
Quoted 5 lines · Oleg Nesterov
But the code looks more complex, and the only advantage is that non-exiting task does
xchg()instead ofcmpxchg(). Not sure this worth the trouble, in this casetask_work_run()will likey run the callbacks (the caller checks ->task_works != NULL), I do not think this can add any noticeable speedup.
Yeah, I agree, the patch doesn't seem worth the trouble. It makes tricky code unreadable at best.
PATCH v2 1/1 task_work: avoid unneeded cmpxchg() in task_work_run()
Quoted 10 lines · Peter Zijlstra
Quoted 5 lines · Oleg Nesterov
But the code looks more complex, and the only advantage is that non-exiting task does
xchg()instead ofcmpxchg(). Not sure this worth the trouble, in this casetask_work_run()will likey run the callbacks (the caller checks ->task_works != NULL), I do not think this can add any noticeable speedup.Yeah, I agree, the patch doesn't seem worth the trouble. It makes tricky code unreadable at best.
To gain better readability, we need to move work_exited things out from task_work_run() too.
Thanks,
Lai
Subject: task_work: avoid unneeded cmpxchg() in task_work_run()
We only require cmpxchg()&retry when task is exiting. xchg() is enough in other cases like original code in ac3d0da8.
So we use xchg() for task_work_run() and move the logic of exit_task_work() out from task_work_run().
---
include/linux/task_work.h +1 -5
@@ -15,10 +15,6 @@ init_task_work(struct callback_head *twork, task_work_func_t func)int task_work_add (struct task_struct *task,struct callback_head *twork,bool );struct callback_head *task_work_cancel (struct task_struct *,task_work_func_t );void task_work_run (void ); - -static inline void exit_task_work (struct task_struct *task) -{ -task_work_run (); -} +void exit_task_work (struct task_struct *task);#endif /* _LINUX_TASK_WORK_H */
kernel/task_work.c +15 -10
@@ -52,16 +52,7 @@ void task_work_run(void)struct callback_head *work, *head, *next;for (;;) { -/* -* work->func() can do task_work_add(), do not set -* work_exited unless the list is empty. -*/ -do { - work =ACCESS_ONCE (task->task_works); - head = !work && (task->flags &PF_EXITING ) ? - &work_exited :NULL ; - }while (cmpxchg (&task->task_works, work, head) != work); - + work =xchg (&task->task_works,NULL );if (!work)break ;/*
@@ -90,3 +81,17 @@ void task_work_run(void) }while (work); } } + +void exit_task_work (struct task_struct *task) +{ +for (;;) { +/* +* work->func() can do task_work_add(), do not set +* work_exited unless the list is empty. +*/ +if (unlikely (task->task_works)) +task_work_run (); +if (cmpxchg (&task->task_works,NULL , &work_exited) ==NULL ) +break ; + } +}
Quoted 29 lines · Lai Jiangshan
kernel/task_work.c +5
-10@@ -52,16 +52,7 @@ void task_work_run(void)struct callback_head *work, *head, *next;for (;;) { -/* -* work->func() can do task_work_add(), do not set -* work_exited unless the list is empty. -*/ -do { - work =ACCESS_ONCE (task->task_works); - head = !work && (task->flags &PF_EXITING ) ? - &work_exited :NULL ; - }while (cmpxchg (&task->task_works, work, head) != work); - + work =xchg (&task->task_works,NULL );if (!work)break ;/* @@ -90,3 +81,17 @@ void task_work_run(void) }while (work); } } + +void exit_task_work (struct task_struct *task) +{ +for (;;) {
kernel/task_work.c void task_work_run(void) · line 97
Quoted 10 lines
kernel/task_work.c +10
-0+/* +* work->func() can do task_work_add(), do not set +* work_exited unless the list is empty. +*/ +if (unlikely (task->task_works)) +task_work_run (); +if (cmpxchg (&task->task_works,NULL , &work_exited) ==NULL ) +break ; + } +}
I agree, this looks fine.
But if you add "unlikely" before task_work_run(), then probably it should do
while (cmpxchg(&task->task_works, NULL, work_exited)) task_work_run();
? it looks more simple/clean.
(OTOH I am not sure "unlikely" is true, note that exit_files() will
offload ____fput() to task_work_run()).
But you did not answer, and I am curious. What was your original motivation? Is xchg really faster than cmpxchg?
Oleg.
Quoted 3 lines · Oleg Nesterov
But you did not answer, and I am curious. What was your original motivation? Is xchg really faster than cmpxchg?
And is this true over multiple architectures? Or are we optimizing for x86_64 (again) ?