Ei vielä käännetty
Tätä sivua ei ole vielä käännetty suomeksi, joten se näytetään englanniksi. Auta kääntämään
pstats Module Complexity¶
The pstats module loads, merges, sorts and prints the statistics that cProfile and profile
collect. A Stats object holds the whole profile in memory: one entry per profiled function, each
carrying a dictionary of the functions that called it. Sorting builds an ordered list of those
entries, and every report copies and filters the entries in that order.
f is the functions in a profile, and e is its caller edges: the entries in all of those caller
dictionaries together. f₂ and e₂ are the same counts for a profile being merged in, and c is
the callers or callees of one printed function. Function names are priced at O(1) to hash, compare
and format. A regular-expression restriction runs its pattern once per candidate name; the
pattern's own cost is not in the bounds.
Complexity Reference¶
Stats¶
| Operation | Time | Space | Notes |
|---|---|---|---|
pstats.Stats(*filenames_or_profiles, stream=None) |
O(f + e) | O(f + e) | For one argument. Loads the first argument and add()s the rest, each at add()'s cost; a profiler is snapshotted with its create_stats(). With no arguments it starts empty |
Stats.add(*filenames_or_profiles) |
O(f₂ + e₂ + e) per profile | O(f₂ + e₂ + e) | A filename or profiler is loaded first. A function both profiles share gets a new caller dictionary holding both sets of callers, so a merge can copy every existing edge. Drops the sort order |
Stats.dump_stats(filename) |
O(f + e) | O(f + e) | Writes the marshal file that Stats(filename) loads |
Stats.strip_dirs() |
O(f + e) | O(f + e) | Rebuilds every key and caller dictionary without directories. Entries that then collide are merged, each merge copying the survivor's callers, so g colliding entries with distinct callers cost O(g²). Drops the sort order |
Stats.sort_stats(*keys) |
O(f log f) | O(f) | Keys are strings, unambiguous prefixes of them, or SortKey members, not a mix; with no keys, reports go back to the unsorted order |
Stats.reverse_order() |
O(f) | O(1) | Reverses the sorted list in place; does nothing before sort_stats() |
Stats.print_stats(*restrictions) |
O(f) | O(f) | Copies the ordered list, then applies each restriction in turn: a string is a regex searched in every remaining name, an int keeps that many, a float in [0, 1) that fraction |
Stats.print_callers(*restrictions) |
O(f + Σ c log c) | O(f + c) | Selects as print_stats() does; each printed function's callers are sorted before printing |
Stats.print_callees(*restrictions) |
O(f + e + Σ c log c) first call, O(f + Σ c log c) after | O(f + e) | The first call inverts the caller graph and caches it. strip_dirs() clears that cache and add() does not, so callees printed after add() omit the merged calls, and a function the merge added prints with none |
Stats.get_stats_profile() |
O(f) | O(f) | Builds a FunctionProfile for every function, in sort order, into a dict keyed by bare function name, so same-named functions in different files overwrite each other |
SortKey¶
| Operation | Time | Space | Notes |
|---|---|---|---|
pstats.SortKey.CALLS, SortKey.CUMULATIVE, SortKey.FILENAME, SortKey.LINE, SortKey.NAME, SortKey.NFL, SortKey.PCALLS, SortKey.STDNAME, SortKey.TIME |
O(1) | O(1) | String enum members; sort_stats() takes them or the strings they stand for |
pstats.SortKey(value) |
O(1) | O(1) | Accepts the alternative spellings too: SortKey('ncalls') is SortKey.CALLS |
FunctionProfile and StatsProfile¶
| Operation | Time | Space | Notes |
|---|---|---|---|
pstats.StatsProfile, StatsProfile.total_tt, StatsProfile.func_profiles |
O(1) | O(1) | What get_stats_profile() returns: total time and a dict of function name to FunctionProfile |
pstats.FunctionProfile, FunctionProfile.ncalls, FunctionProfile.tottime, FunctionProfile.percall_tottime, FunctionProfile.cumtime, FunctionProfile.percall_cumtime, FunctionProfile.file_name, FunctionProfile.line_number |
O(1) | O(1) | Dataclass fields; ncalls is a string, '3/1' for three calls of which one was primitive |
Loading and Saving Profiles¶
A Stats object is the whole profile: loading one is linear in its functions and caller edges,
and so is writing it back out. A profiler can be handed over directly, without a file.
import cProfile
import io
import os
import pstats
import tempfile
def leaf(n):
return n * 2
def branch(n):
total = 0
for i in range(3):
total += leaf(n + i)
return total
profiler = cProfile.Profile()
profiler.runcall(branch, 10)
stats = pstats.Stats(profiler, stream=io.StringIO()) # O(f + e)
profile = stats.get_stats_profile() # O(f)
assert profile.func_profiles['leaf'].ncalls == '3'
assert profile.func_profiles['branch'].ncalls == '1'
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, 'run.prof')
stats.dump_stats(path) # O(f + e)
reloaded = pstats.Stats(path, stream=io.StringIO()) # O(f + e)
assert reloaded.get_stats_profile().func_profiles.keys() == profile.func_profiles.keys()
Merging Runs¶
add() sums the counts of every function the two profiles share and unions their callers. Each
shared function gets a fresh caller dictionary, so merging a small profile into a large one can
still copy the large one's edges. An empty Stats() can start a merge loop.
import cProfile
import io
import pstats
def work():
return sum(range(100))
combined = pstats.Stats(stream=io.StringIO()) # O(1) - empty
for _ in range(3):
profiler = cProfile.Profile()
profiler.runcall(work)
combined.add(profiler) # O(f₂ + e₂ + e)
assert combined.get_stats_profile().func_profiles['work'].ncalls == '3'
Sorting¶
sort_stats() builds a list of every entry and sorts it: O(f log f) whichever keys you pass, and
later keys only break ties. add() and strip_dirs() both discard the order, so sort last.
import cProfile
import io
import pstats
def leaf(n):
return n * 2
def branch(n):
total = 0
for i in range(3):
total += leaf(n + i)
return total
profiler = cProfile.Profile()
profiler.runcall(branch, 10)
stats = pstats.Stats(profiler, stream=io.StringIO())
stats.sort_stats(pstats.SortKey.CALLS, pstats.SortKey.NAME) # O(f log f)
order = list(stats.get_stats_profile().func_profiles)
assert order[0] == 'leaf' # three calls, the most
stats.reverse_order() # O(f)
assert list(stats.get_stats_profile().func_profiles) == order[::-1]
# Strings and their unambiguous prefixes work too, but not mixed with SortKey
stats.sort_stats('ncalls', 'name') # O(f log f)
assert list(stats.get_stats_profile().func_profiles) == order
try:
stats.sort_stats('calls', pstats.SortKey.NAME)
except TypeError as error:
assert 'mixed' in str(error)
else:
raise AssertionError('a string and a SortKey were mixed')
stats.strip_dirs() # O(f + e) - and the order is gone
stats.sort_stats(pstats.SortKey.CALLS) # sort again after it
Restricting Reports¶
Every print method copies the ordered list and filters it, so even print_stats(1) is O(f).
Restrictions apply left to right, each to what the previous one kept: a regex placed after a count
is searched in only the entries the count kept.
import cProfile
import io
import pstats
def leaf(n):
return n * 2
def branch(n):
total = 0
for i in range(3):
total += leaf(n + i)
return total
profiler = cProfile.Profile()
profiler.runcall(branch, 10)
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream).sort_stats('calls')
stats.print_stats(1, r'\(branch\)') # O(f) - the top entry, then the regex on it
assert '(branch)' not in stream.getvalue() # the top entry is leaf
stream.seek(0)
stream.truncate()
stats.print_stats(r'\(branch\)', 1) # O(f) - the regex over all f, then one match
assert '(branch)' in stream.getvalue()
Walking the Call Graph¶
print_callers() reads the caller dictionaries each entry already holds. print_callees() needs
the reverse direction, which it builds for the whole profile on its first call and keeps.
strip_dirs() clears it; add() does not, so call print_callees() only after the last merge.
import cProfile
import io
import pstats
def leaf(n):
return n * 2
def branch(n):
total = 0
for i in range(3):
total += leaf(n + i)
return total
profiler = cProfile.Profile()
profiler.runcall(branch, 10)
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream).strip_dirs()
stats.print_callers('leaf') # O(f + c log c) - callers are stored per entry
callers = stream.getvalue()
assert 'was called by' in callers and '(branch)' in callers
stream.seek(0)
stream.truncate()
stats.print_callees('branch') # O(f + e) the first time - inverts the graph
assert 'called...' in stream.getvalue() and '(leaf)' in stream.getvalue()
Common Patterns¶
Profile, Trim and Report¶
Every method in the Stats table except get_stats_profile() and dump_stats() returns the
Stats object, so a report is one chain. Stripping directories before sorting keeps the order the report needs.
import cProfile
import io
import pstats
def parse(text):
return text.split(',')
def run():
rows = []
for _ in range(50):
rows.append(parse('a,b,c'))
return rows
profiler = cProfile.Profile()
profiler.runcall(run)
stream = io.StringIO()
(pstats.Stats(profiler, stream=stream)
.strip_dirs() # O(f + e)
.sort_stats(pstats.SortKey.CUMULATIVE) # O(f log f)
.print_stats(10)) # O(f)
report = stream.getvalue()
assert 'Ordered by: cumulative time' in report
assert '(parse)' in report
Performance Best Practices¶
✅ Do:
- Call
strip_dirs()andadd()beforesort_stats(): both drop the order, and sorting is the O(f log f) step - Put a count before a regex in a print restriction when the top entries are all you want: the regex then searches only those
- Merge every run before the first
print_callees(), which caches the callee mapadd()does not refresh - Hand a profiler straight to
Stats()instead of round-tripping it throughdump_stats()
❌ Avoid:
- Looking a function up in
get_stats_profile()by name when several files define it: only one of them survives strip_dirs()when files in different directories share names, lines and functions: their entries merge, and g such collisions with distinct callers cost O(g²)- Re-sorting inside a loop that prints: sort once, then print with different restrictions
Version Notes¶
- Python 3.11+:
SortKeyis aStrEnum, sostr(SortKey.TIME)is'time'; on 3.10 it is'SortKey.TIME'