Don't apply for write lock on tasklist_lock if parent doesn't ptrace other processes
PATCH 1/1 Don't apply for write lock on tasklist_lock if parent doesn't ptrace other processes
We run some sub-cases (fork, exec, pipe, tcp, udp) of aim7 on 8-socket machine. Perf shows write_lock_irq(&tasklist_lock) consumes more than 50% cpu time.
One hot caller is exit_ptrace. If the exiting process doesn't ptrace other processes, kernel needn't apply for the write lock on tasklist_lock.
With below patch against kernel 2.6.35-rc5, we get more than 10% result improvement.
---
kernel/ptrace.c +3 -0
@@ -331,6 +331,9 @@ void exit_ptrace(struct task_struct *trastruct task_struct *p, *n;LIST_HEAD (ptrace_dead); +if (list_empty (&tracer->ptraced)) +return ; +write_lock_irq (&tasklist_lock);list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))
Quoted 9 lines · Yanmin
We run some sub-cases (fork, exec, pipe, tcp, udp) of aim7 on 8-socket machine. Perf shows
write_lock_irq(&tasklist_lock)consumes more than 50% cpu time.One hot caller is
exit_ptrace. If the exiting process doesn't ptrace other processes, kernel needn't apply for the write lock ontasklist_lock.With below patch against kernel 2.6.35-rc5, we get more than 10% result improvement.
Signed-off-by Zhang Yanmin <[email protected]>
We're guarded against ptrace_attach() because tracer->exit_state is non-zero at this point in the exit path.
Quoted 16 lines · Yanmin
We run some sub-cases (fork, exec, pipe, tcp, udp) of aim7 on 8-socket machine. Perf shows
write_lock_irq(&tasklist_lock)consumes more than 50% cpu time.One hot caller is
exit_ptrace. If the exiting process doesn't ptrace other processes, kernel needn't apply for the write lock ontasklist_lock.With below patch against kernel 2.6.35-rc5, we get more than 10% result improvement.
Signed-off-by Zhang Yanmin <[email protected]>---
diff -Nraup
linux-2.6.35-rc5/kernel/ptrace.clinux-2.6.35-rc5_ptrace/kernel/ptrace.c
kernel/ptrace.c void exit_ptrace(struct task_struct *tra · line 339
Quoted 9 lines
kernel/ptrace.c +3
-0@@ -331,6 +331,9 @@ void exit_ptrace(struct task_struct *trastruct task_struct *p, *n;LIST_HEAD (ptrace_dead); +if (list_empty (&tracer->ptraced)) +return ; +write_lock_irq (&tasklist_lock);list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))
hah, nice patch - an easy 10%. I snuck a cc:stable into the changelog in the hope that those guys mistake it for a bugfix ;)
Quoted 10 lines
kernel/ptrace.c void exit_ptrace(struct task_struct *tra · line 339
Quoted 10 lines
kernel/ptrace.c +3
-0@@ -331,6 +331,9 @@ void exit_ptrace(struct task_struct *trastruct task_struct *p, *n;LIST_HEAD (ptrace_dead); +if (list_empty (&tracer->ptraced)) +return ; +write_lock_irq (&tasklist_lock);list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))
I think we may have tried that before. Oleg can tell us if it's really safe vs a race with PTRACE_TRACEME or something like that.
Thanks,
Roland
Oleg Nesterov
maintainerI am not surpized perf blaims tasklist, but I am really surpized this patch adds 10% improvement...
Quoted 5 lines · Roland McGrath
Quoted 5 lines
Quoted 5 lines
(quoted diff) +0
-0@@ -331,6 +331,9 @@ void exit_ptrace(struct task_struct *tra struct task_struct *p, *n; LIST_HEAD(ptrace_dead);
Quoted 9 lines
Quoted 6 lines
Review void exit_ptrace(struct task_struct *tra · line 339
Quoted 6 lines
(quoted diff) +3
-0+ if (list_empty(&tracer->ptraced)) + return; + write_lock_irq(&tasklist_lock); list_for_each_entry_safe(p, n, &tracer->ptraced, ptrace_entry) { if (__ptrace_detach(tracer, p))I think we may have tried that before. Oleg can tell us if it's really safe vs a race with
PTRACE_TRACEMEor something like that.
Yes, this can race with ptrace_traceme(). Without tasklist_lock in exit_ptrace(), it is possible that ptrace_traceme() starts __ptrace_link() before it sees PF_EXITING, and completes before the result of list_add() is visible to the exiting parent. tasklist acts as a barrier.
So, this list_empty() check needs taskslit at least for reading. But, we are going to take it for writing right after exit_ptrace() returns, afaics we can add this fastpatch check for free.
Uncompiled/untested.
Oleg.
kernel/ptrace.c | 10 +++++++--- kernel/exit.c | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-)
kernel/ptrace.c +7 -3
@@ -324,26 +324,30 @@ int ptrace_detach(struct task_struct *ch }/* -* Detach all tasks we were using ptrace on. +* Detach all tasks we were using ptrace on. Called with tasklist held. */ void exit_ptrace (struct task_struct *tracer) {struct task_struct *p, *n;LIST_HEAD (ptrace_dead); -write_lock_irq (&tasklist_lock); +if (likely (list_empty (&tracer->ptraced))) +return ; +list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))list_add (&p->ptrace_entry, &ptrace_dead); } -write_unlock_irq (&tasklist_lock); +write_unlock_irq (&tasklist_lock);BUG_ON (!list_empty (&tracer->ptraced));list_for_each_entry_safe (p, n, &ptrace_dead, ptrace_entry) {list_del_init (&p->ptrace_entry);release_task (p); } + +write_lock_irq (&tasklist_lock); }int ptrace_readdata (struct task_struct *tsk,unsigned long src,char __user *dst,int len)
kernel/exit.c +2 -1
@@ -771,9 +771,10 @@ static void forget_original_parent(strucstruct task_struct *p, *n, *reaper;LIST_HEAD (dead_children); +write_lock_irq (&tasklist_lock); +exit_ptrace (father); -write_lock_irq (&tasklist_lock); reaper =find_new_reaper (father);list_for_each_entry_safe (p, n, &father->children, sibling) {
Quoted 3 lines
So, this
list_empty()check needs taskslit at least for reading. But, we are going to take it for writing right afterexit_ptrace()returns, afaics we can add this fastpatch check for free.
That looks good to me, but it could use some more scare comments.
Quoted 3 lines
/*
- * Detach all tasks we were using ptrace on.
+ * Detach all tasks we were using ptrace on. Called with tasklist held.
* Called with tasklist held for writing, and returns with it held too.
* But note it can release and reacquire the lock.
kernel/exit.c static void forget_original_parent(struc · line 775
Quoted 2 lines
kernel/exit.c +2
-0+write_lock_irq (&tasklist_lock); +
/* * Note that exit_ptrace() might drop tasklist_lock and reacquire it. */
kernel/exit.c static void forget_original_parent(struc · line 778
Quoted 4 lines
kernel/exit.c +0
-1exit_ptrace (father); -write_lock_irq (&tasklist_lock); reaper =find_new_reaper (father);
Thanks,
Roland
Oleg Nesterov
maintainerQuoted 6 lines · Roland McGrath
Quoted 4 lines
So, this
list_empty()check needs taskslit at least for reading. But, we are going to take it for writing right afterexit_ptrace()returns, afaics we can add this fastpatch check for free.That looks good to me, but it could use some more scare comments.
Good. Hopfully Zhang can test it to confirm it has the same effect. It should, but I am still wondering about 10% improvement.
Quoted 6 lines
Quoted 3 lines
/*
- * Detach all tasks we were using ptrace on.
+ * Detach all tasks we were using ptrace on. Called with tasklist held.* Called with tasklist held for writing, and returns with it held too.
* But note it can release and reacquire the lock.
OK.
Quoted 6 lines
kernel/exit.c static void forget_original_parent(struc · line 775
Quoted 2 lines
kernel/exit.c +2
-0+write_lock_irq (&tasklist_lock); +/* * Note that exit_ptrace() might drop tasklist_lock and reacquire it. */Quoted 1 line
exit_ptrace(father);
Well, this comment a bit "unfair", please see below.
Quoted 2 lines
kernel/exit.c static void forget_original_parent(struc · line 778
Quoted 2 lines
kernel/exit.c +0
-1-write_lock_irq (&tasklist_lock); reaper =find_new_reaper (father);
Note that find_new_reaper() can drop/reacquire tasklist too.
Perhaps,
/* These two might drop and reacquire tasklist_lock */ exit_ptrace(father); reaper = find_new_reaper(father); ...
?
Oleg.
Quoted 2 lines · Oleg Nesterov
I am not surpized perf blaims tasklist, but I am really surpized this patch adds 10% improvement...
I changed aim7 workfile to focus on fork/exec and other a couple of sub-cases. And this behavior is clear on 8-socket machines.
Quoted 17 lines · Roland McGrath
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
kernel/ptrace.c +0
-0@@ -331,6 +331,9 @@ void exit_ptrace(struct task_struct *trastruct task_struct *p, *n;LIST_HEAD (ptrace_dead);Quoted 9 lines
Quoted 6 lines
kernel/ptrace.c int ptrace_detach(struct task_struct *ch · line 338
Quoted 6 lines
kernel/ptrace.c +3
-0+if (list_empty (&tracer->ptraced)) +return ; +write_lock_irq (&tasklist_lock);list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))I think we may have tried that before. Oleg can tell us if it's really safe vs a race with
PTRACE_TRACEMEor something like that.
Quoted 4 lines
Yes, this can race with
ptrace_traceme(). Withouttasklist_lockinexit_ptrace(), it is possible thatptrace_traceme()starts__ptrace_link()before it seesPF_EXITING, and completes before the result oflist_add()is visible to the exiting parent. tasklist acts as a barrier.
Thanks for your kind explanation.
kernel/exit.c static void forget_original_parent(struc
Quoted 52 lines
So, this
list_empty()check needs taskslit at least for reading. But, we are going to take it for writing right afterexit_ptrace()returns, afaics we can add this fastpatch check for free.Uncompiled/untested.
Oleg.
kernel/ptrace.c | 10 +++++++--- kernel/exit.c | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-)kernel/ptrace.c +7
-3@@ -324,26 +324,30 @@ int ptrace_detach(struct task_struct *ch }/* -* Detach all tasks we were using ptrace on. +* Detach all tasks we were using ptrace on. Called with tasklist held. */ void exit_ptrace (struct task_struct *tracer) {struct task_struct *p, *n;LIST_HEAD (ptrace_dead); -write_lock_irq (&tasklist_lock); +if (likely (list_empty (&tracer->ptraced))) +return ; +list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))list_add (&p->ptrace_entry, &ptrace_dead); } -write_unlock_irq (&tasklist_lock); +write_unlock_irq (&tasklist_lock);BUG_ON (!list_empty (&tracer->ptraced));list_for_each_entry_safe (p, n, &ptrace_dead, ptrace_entry) {list_del_init (&p->ptrace_entry);release_task (p); } + +write_lock_irq (&tasklist_lock); }int ptrace_readdata (struct task_struct *tsk,unsigned long src,char __user *dst,int len)kernel/exit.c +0
-0@@ -771,9 +771,10 @@ static void forget_original_parent(struc
After applying my patch (although it's incorrect as there is a race with TRACEME), perf shows write_lock_irq in forget_original_parent consumes less than 40% cpu time on 8-socket machine.
Is it possible to optimize it to use finer locks instead of the global tasklist_lock?
Quoted 12 lines
kernel/exit.c +2
-1struct task_struct *p, *n, *reaper;LIST_HEAD (dead_children); +write_lock_irq (&tasklist_lock); +exit_ptrace (father); -write_lock_irq (&tasklist_lock); reaper =find_new_reaper (father);list_for_each_entry_safe (p, n, &father->children, sibling) {
Oleg Nesterov
maintainerQuoted 6 lines · Yanmin
Quoted 2 lines · Oleg Nesterov
I am not surpized perf blaims tasklist, but I am really surpized this patch adds 10% improvement...
I changed aim7 workfile to focus on fork/exec and other a couple of sub-cases. And this behavior is clear on 8-socket machines.
Thanks...
Quoted 3 lines
After applying my patch (although it's incorrect as there is a race with TRACEME), perf shows
write_lock_irqinforget_original_parentconsumes less than 40% cpu time on 8-socket machine.
Any chance you can test the patch I sent? It should have the same effect, otherwise there is something interesting.
Quoted 1 line
Is it possible to optimize it to use finer locks instead of the global
tasklist_lock?
Heh. We must optimize it. But it is not clear when ;)
Oleg.
Quoted 8 lines · Oleg Nesterov
Quoted 6 lines · Yanmin
Quoted 2 lines · Oleg Nesterov
I am not surpized perf blaims tasklist, but I am really surpized this patch adds 10% improvement...
I changed aim7 workfile to focus on fork/exec and other a couple of sub-cases. And this behavior is clear on 8-socket machines.
Quoted 8 lines
Thanks...
Quoted 3 lines
After applying my patch (although it's incorrect as there is a race with TRACEME), perf shows
write_lock_irqinforget_original_parentconsumes less than 40% cpu time on 8-socket machine.Any chance you can test the patch I sent? It should have the same effect, otherwise there is something interesting.
1) with my patch, we got about 13% improvement;
2) With your patch, we got about 11% improvement;
Performance is very sensitive to spinlock contention on large machines.
Quoted 4 lines
Quoted 2 lines
Is it possible to optimize it to use finer locks instead of the global
tasklist_lock?Heh. We must optimize it. But it is not clear when ;)
Thanks. It's better to remove the big lock.
Oleg Nesterov
maintainerQuoted 8 lines · Yanmin
Quoted 6 lines · Oleg Nesterov
Quoted 4 lines · Yanmin
After applying my patch (although it's incorrect as there is a race with TRACEME), perf shows
write_lock_irqinforget_original_parentconsumes less than 40% cpu time on 8-socket machine.
Quoted 6 lines
Quoted 2 lines
Any chance you can test the patch I sent? It should have the same effect, otherwise there is something interesting.
1) with my patch, we got about 13% improvement;
2) With your patch, we got about 11% improvement;Performance is very sensitive to spinlock contention on large machines.
Zhang, thank you very much.
But. In this case I do not trust these results or I missed something. I mean, they do not look 100% accurate.
With your patch:
forget_original_parent: exit_ptrace: if (list_empty(ptraced)) return; write_lock_irq(tasklist); ... do a lot more work ...
With my patch:
forget_original_parent: write_lock_irq(tasklist); exit_ptrace: if (list_empty(ptraced)) return; ... do a lot more work ...
The only difference is that we are doing the function call + list_empty() under tasklist, just a few instructions compared to "do a lot more work" in forget_original_parent().
How this can make the 2% difference ? This looks like a noise to me, or do you think I missed something?
Quoted 2 lines
Quoted 1 line
Heh. We must optimize it. But it is not clear when ;)
Thanks. It's better to remove the big lock.
Yes. The only problem this is very much nontrival with the current code.
Oleg.
Quoted 8 lines
The only difference is that we are doing the function call +
list_empty()
under tasklist, just a few instructions compared to "do a lot more work"
inforget_original_parent().How this can make the 2% difference ? This looks like a noise to me, or do you think I missed something?
It could be a cache miss or something like that. Instructions are not all the same cost. Only detailed profiling with different performance counters could give you more information.
-Andi
Quoted 46 lines · Oleg Nesterov
Quoted 8 lines · Yanmin
Quoted 6 lines · Oleg Nesterov
Quoted 4 lines · Yanmin
After applying my patch (although it's incorrect as there is a race with TRACEME), perf shows
write_lock_irqinforget_original_parentconsumes less than 40% cpu time on 8-socket machine.Quoted 6 lines
Quoted 2 lines
Any chance you can test the patch I sent? It should have the same effect, otherwise there is something interesting.
1) with my patch, we got about 13% improvement;
2) With your patch, we got about 11% improvement;Performance is very sensitive to spinlock contention on large machines.
Zhang, thank you very much.
But. In this case I do not trust these results or I missed something. I mean, they do not look 100% accurate.
With your patch:
forget_original_parent: exit_ptrace: if (list_empty(ptraced)) return; write_lock_irq(tasklist); ... do a lot more work ...With my patch:
forget_original_parent: write_lock_irq(tasklist); exit_ptrace: if (list_empty(ptraced)) return; ... do a lot more work ...
Quoted 3 lines
The only difference is that we are doing the function call +
list_empty()under tasklist, just a few instructions compared to "do a lot more work" inforget_original_parent().
If considering lock acquire/release on a big machine, plus cache-misses like what Andi said, the result is reasonable. We did lots of testing on 8-socket machine. Performance result is very sensitive to lock contentions and cache-misses.
Quoted 2 lines
How this can make the 2% difference ?
I reran the testing for a couple of times to make sure the result is stable.
Quoted 2 lines
This looks like a noise to me,
or do you think I missed something?
No, you didn't miss anything. Any patch shouldn't introduce bugs, so your patch is right and good.
Quoted 5 lines
Quoted 3 lines
Quoted 2 lines
Heh. We must optimize it. But it is not clear when ;)
Thanks. It's better to remove the big lock.
Yes. The only problem this is very much nontrival with the current code.
I agree that would be a big project.
Oleg Nesterov
maintainerPATCH 1/1 ptrace: optimize exit_ptrace() for the likely case
(replaces ptrace-dont-run-write_locktasklist_lock-if-the-parent-doesnt-ptrace-other-processes.patch)
exit_ptrace() takes tasklist_lock unconditionally. We need this lock to avoid the race with ptrace_traceme(), it acts as a barrier.
Change its caller, forget_original_parent(), to call exit_ptrace() under tasklist_lock. Change exit_ptrace() to drop and reacquire this lock if needed.
This allows us to add the fastpath list_empty(ptraced) check. In the likely no-tracees case exit_ptrace() just returns and we avoid the lock() + unlock() sequence.
"Zhang, Yanmin" <[email protected]> suggested to add this check, and he reports that this change adds about 11% improvement in some tests.
---
kernel/ptrace.c | 12 +++++++++--- kernel/exit.c | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-)
kernel/ptrace.c renamed from kernel/ptrace.c~exit_ptrace_fastpath_check +9 -3
@@ -324,26 +324,32 @@ int ptrace_detach(struct task_struct *ch }/* -* Detach all tasks we were using ptrace on. +* Detach all tasks we were using ptrace on. Called with tasklist held +* for writing, and returns with it held too. But note it can release +* and reacquire the lock. */ void exit_ptrace (struct task_struct *tracer) {struct task_struct *p, *n;LIST_HEAD (ptrace_dead); -write_lock_irq (&tasklist_lock); +if (likely (list_empty (&tracer->ptraced))) +return ; +list_for_each_entry_safe (p, n, &tracer->ptraced, ptrace_entry) {if (__ptrace_detach (tracer, p))list_add (&p->ptrace_entry, &ptrace_dead); } -write_unlock_irq (&tasklist_lock); +write_unlock_irq (&tasklist_lock);BUG_ON (!list_empty (&tracer->ptraced));list_for_each_entry_safe (p, n, &ptrace_dead, ptrace_entry) {list_del_init (&p->ptrace_entry);release_task (p); } + +write_lock_irq (&tasklist_lock); }int ptrace_readdata (struct task_struct *tsk,unsigned long src,char __user *dst,int len)
kernel/exit.c renamed from kernel/exit.c~exit_ptrace_fastpath_check +5 -2
@@ -771,9 +771,12 @@ static void forget_original_parent(strucstruct task_struct *p, *n, *reaper;LIST_HEAD (dead_children); -exit_ptrace (father); -write_lock_irq (&tasklist_lock); +/* +* Note that exit_ptrace() and find_new_reaper() might +* drop tasklist_lock and reacquire it. +*/ +exit_ptrace (father); reaper =find_new_reaper (father);list_for_each_entry_safe (p, n, &father->children, sibling) {
Re: [PATCH] ptrace: optimize exit_ptrace() for the likely case