Skip to content

Commit

Permalink
Faster Process.children(recursive=True) (#1186)
Browse files Browse the repository at this point in the history
Before:
```
$ python -m timeit -s "import psutil; p = psutil.Process()" "list(p.children(recursive=True))"
10 loops, best of 3: 29.6 msec per loop
```

After:
```
$ python -m timeit -s "import psutil; p = psutil.Process()" "list(p.children(recursive=True))"
100 loops, best of 3: 12.4 msec per loop
```

For reference, non-recursive:
```
$ python -m timeit -s "import psutil; p = psutil.Process()" "list(p.children())"
100 loops, best of 3: 12.2 msec per loop
```
  • Loading branch information
pitrou authored and giampaolo committed Dec 2, 2017
1 parent f0094db commit f1d94cc
Showing 1 changed file with 20 additions and 20 deletions.
40 changes: 20 additions & 20 deletions psutil/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,33 +888,33 @@ def children(self, recursive=False):
except (NoSuchProcess, ZombieProcess):
pass
else:
# construct a dict where 'values' are all the processes
# having 'key' as their parent
table = collections.defaultdict(list)
# Construct a {pid: [child pids]} dict
reverse_ppid_map = collections.defaultdict(list)
for pid, ppid in ppid_map.items():
try:
p = Process(pid)
table[ppid].append(p)
except (NoSuchProcess, ZombieProcess):
pass
# At this point we have a mapping table where table[self.pid]
# are the current process' children.
# Below, we look for all descendants recursively, similarly
# to a recursive function call.
checkpids = [self.pid]
for pid in checkpids:
for child in table[pid]:
reverse_ppid_map[ppid].append(pid)
# Recursively traverse that dict, starting from self.pid,
# such that we only call Process() on actual children
seen = set()
stack = [self.pid]
while stack:
pid = stack.pop()
if pid in seen:
# Since pids can be reused while the ppid_map is
# constructed, there may be rare instances where
# there's a cycle in the recorded process "tree".
continue
seen.add(pid)
for child_pid in reverse_ppid_map[pid]:
try:
child = Process(child_pid)
# if child happens to be older than its parent
# (self) it means child's PID has been reused
intime = self.create_time() <= child.create_time()
except (NoSuchProcess, ZombieProcess):
pass
else:
if intime:
ret.append(child)
if child.pid not in checkpids:
checkpids.append(child.pid)
stack.append(child_pid)
except (NoSuchProcess, ZombieProcess):
pass
return ret

def cpu_percent(self, interval=None):
Expand Down

0 comments on commit f1d94cc

Please sign in to comment.