diff options
author | Antoine Pitrou <pitrou@free.fr> | 2017-12-02 10:07:06 +0100 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2017-12-02 10:07:06 +0100 |
commit | f1d94cc22afdba6cb61c5a0ab246e877a83080ab (patch) | |
tree | 0ba171bfdf88a8406a6a72cb7118b6d5af489b6f | |
parent | f0094db79ad9e4f2246997cb8c2046b71c465a29 (diff) | |
download | psutil-f1d94cc22afdba6cb61c5a0ab246e877a83080ab.tar.gz |
Faster Process.children(recursive=True) (#1186)
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
```
-rw-r--r-- | psutil/__init__.py | 40 |
1 files changed, 20 insertions, 20 deletions
diff --git a/psutil/__init__.py b/psutil/__init__.py index a8447973..9a422cda 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -888,33 +888,33 @@ class Process(object): 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): |