sc_profiling
Profiling and CPU/memory management functions.
Highlights
sc.profile(): a line profilersc.cprofile(): a function profilersc.benchmark(): quickly check your computer’s performancesc.resourcemonitor(): a monitor to kill processes that exceed memory or other limits
Classes
| Name | Description |
|---|---|
| LimitExceeded | Custom exception for use with the sc.resourcemonitor() monitor. |
| cprofile | Function profiler, built off Python’s built-in cProfile |
| profile | Profile the line-by-line time required by a function. |
| resourcemonitor | Asynchronously monitor resource (e.g. memory) usage and terminate the process |
| tracecalls | Trace all function calls. |
LimitExceeded
sc_profiling.LimitExceeded()Custom exception for use with the sc.resourcemonitor() monitor.
It inherits from MemoryError since this is the most similar built-in Python except, and it inherits from KeyboardInterrupt since this is the means by which the monitor interrupts the main Python thread.
cprofile
sc_profiling.cprofile(
sort='cumtime',
columns='default',
mintime=0.001,
maxitems=100,
maxfunclen=40,
maxpathlen=40,
use_ms=None,
stripdirs=True,
show=True,
)Function profiler, built off Python’s built-in cProfile
Note: sc.profile() shows the time taken by each line of code, in the order the code appears in. sc.cprofile() shows the time taken by each function, regardless of where in the code it appears.
The profiler can be used either with the enable() and disable() commands, or as a context block. See examples below for details.
Default columns of output are:
- 'func': the function being called
- 'cumpct': the cumulative percentage of time taken by this function (including subfunctions)
- 'selfpct': the percentage of time taken just by this function (excluding subfunctions)
- 'cumtime': the cumulative time taken by this function
- 'selftime': the time taken just by this function
- 'calls': the number of calls
- 'path': the file and line number
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| sort | str | the column to sort by (default “cumpct”) | 'cumtime' |
| columns | str | what columns to show; options are “default” (above), “brief” (just func, cumtime, selftime), and “full” (as default plus percall and separate line numbers) | 'default' |
| mintime | float | exclude function times below this value | 0.001 |
| maxitems | int | only include up to this many functions in the output | 100 |
| maxfunclen | int | maximum length of the function name to print | 40 |
| maxpathlen | int | maximum length of the function path to print | 40 |
| use_ms | bool | if True, convert to milliseconds; if None, scale if and only if all durations are <1 second | None |
| stripdirs | bool | whether to strip folder information from the file paths | True |
| show | bool | whether to show results of the profiling as soon as it’s complete | True |
Examples:
import sciris as sc
import numpy as np
class Slow:
def math(self):
n = 1_000_000
self.a = np.arange(n)
self.b = sum(self.a)
def plain(self):
n = 100_000
self.int_list = []
self.int_dict = {}
for i in range(n):
self.int_list.append(i)
for j in range(10):
self.int_dict[i+j] = i+j
def run(self):
self.math()
self.plain()
# Option 1: as a context block
with sc.cprofile() as cpr:
slow = Slow()
slow.run()
# Option 2: with start and stop
cpr = sc.cprofile()
cpr.start()
slow = Slow()
slow.run()
cpr.stop()- New in version 3.1.6.
- New in version 3.2.4: “use_ms” argument
Methods
| Name | Description |
|---|---|
| disp | Display the results of the profiling; arguments are passed to to_df() |
| parse_stats | Parse the raw data into a dictionary |
| start | Start profiling |
| stop | Stop profiling |
| to_df | Parse data into a dataframe |
disp
sc_profiling.cprofile.disp(*args, **kwargs)Display the results of the profiling; arguments are passed to to_df()
parse_stats
sc_profiling.cprofile.parse_stats(stripdirs=None, force=False)Parse the raw data into a dictionary
start
sc_profiling.cprofile.start()Start profiling
stop
sc_profiling.cprofile.stop()Stop profiling
to_df
sc_profiling.cprofile.to_df(
sort=None,
mintime=None,
maxitems=None,
maxfunclen=None,
maxpathlen=None,
columns=None,
)Parse data into a dataframe
See class docstring for arguments
profile
sc_profiling.profile(
run,
follow=None,
private='__init__',
include=None,
exclude=None,
unwrap=True,
skipzero=False,
do_run=True,
verbose=True,
*args,
**kwargs,
)Profile the line-by-line time required by a function.
Interface to the line_profiler library.
Note: sc.profile() shows the time taken by each line of code, in the order the code appears in. sc.cprofile() shows the time taken by each function, regardless of where in the code it appears.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| run | function |
The function to be run | required |
| follow | function |
The function, list of functions, class, or module to be followed in the profiler; if None, defaults to the run function | None |
| private | bool / str / list | if True and a class is supplied, follow private functions; if a string/list, follow only those private functions (default '__init__') |
'__init__' |
| include | str | if a class/module is supplied, include only functions matching this string | None |
| exclude | str | if a class/module is supplied, exclude functions matching this string | None |
| unwrap | bool | if true (default), then unwrap functions wrapped by decorators (otherwise, the decorator is profiled) | True |
| skipzero | bool | skip functions with 0 time (i.e. that were not run); default false (i.e. do include them) | False |
| do_run | bool | whether to run immediately (default: true) | True |
| print_stats | bool | whether to print the statistics of the profile to stdout (default True) | required |
| verbose | bool | list the functions to be profiled | True |
| args | list | Passed to the function to be run | () |
| kwargs | dict | Passed to the function to be run | {} |
Returns
| Name | Type | Description |
|---|---|---|
| LineProfiler (by default, the profile output is also printed to stdout) |
Example:
def slow_fn():
n = 10000
int_list = []
int_dict = {}
for i in range(n):
int_list.append(i)
int_dict[i] = i
return
class Foo:
def __init__(self, a=0):
self.a = a
def outer(self):
for i in range(100):
self.inner()
def inner(self):
for i in range(1000):
self.a += 1
# Profile a function
sc.profile(slow_fn)
# Profile a class or class instance
foo = Foo()
sc.profile(run=foo.outer, follow=foo)
# Profile the constructor for Foo
f = lambda a: Foo(a)
sc.profile(run=f, follow=Foo.__init__, a=10) # "a" is passed to the function- New in version 3.2.0: allow class and module arguments for “follow”; “private” argument
- New in version 3.2.2: converted to a class
- New in version 3.2.4: “merge” method, “unwrap” argument
Methods
| Name | Description |
|---|---|
| disp | Display the results of the profiling |
| merge | Allow multiple profilers to be combined (to be able to do combined stats) |
| parse_follow | Do processing on the functions |
| plot | Plot the time spent on each function. |
| run | Run profiling |
| sort | Sort or unsort by time. |
disp
sc_profiling.profile.disp(bytime=1, maxentries=10, skiprun=False)Display the results of the profiling
merge
sc_profiling.profile.merge(other, inplace=False, swap=False, overwrite=True)Allow multiple profilers to be combined (to be able to do combined stats)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| other | sc.profile |
the other sc.profile instance to merge |
required |
| inplace | bool | if True, modify this instance in place | False |
| swap | bool | if True, put “other” first | False |
| overwrite | bool | if True, overwrite duplicates (with the one that took more time) | True |
- New in version 3.2.4.
parse_follow
sc_profiling.profile.parse_follow(strict=False)Do processing on the functions
plot
sc_profiling.profile.plot(
bytime=1,
maxentries=10,
figkwargs=None,
barkwargs=None,
)Plot the time spent on each function.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| bytime | bool | if True, order events by total time rather than actual order | 1 |
| maxentries | int | how many entries to show | 10 |
| figkwargs | dict | passed to plt.figure() |
None |
| barkwargs | dict | passed to plt.bar() |
None |
run
sc_profiling.profile.run(disp=None)Run profiling
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| disp | bool | whether to display results (self.disp()) after run; if None, use self.verbose value |
None |
sort
sc_profiling.profile.sort(bytime=1, copy=False)Sort or unsort by time.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| bytime | int | if 1, sort by increasing time (default); if -1, sort by decreasing; if 0, do not sort by time | 1 |
resourcemonitor
sc_profiling.resourcemonitor(
mem=0.9,
cpu=None,
time=None,
interval=1.0,
label=None,
start=True,
die=True,
kill_children=True,
kill_parent=False,
callback=None,
verbose=None,
)Asynchronously monitor resource (e.g. memory) usage and terminate the process if the specified threshold is exceeded.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| mem | float | maximum virtual memory allowed (as a fraction of total RAM) | 0.9 |
| cpu | float | maximum CPU usage (NB: included for completeness only; typically one would not terminate a process just due to high CPU usage) | None |
| time | float | maximum time limit in seconds | None |
| interval | float | how frequently to check memory/CPU usage (in seconds) | 1.0 |
| label | str | an optional label to use while printing out progress | None |
| start | bool | whether to start the resource monitor on initialization (else call start()) |
True |
| die | bool | whether to raise an exception if the resource limit is exceeded | True |
| kill_children | bool | whether to kill child processes (if False, will not work with multiprocessing) | True |
| kill_parent | bool | whether to also kill the parent process (will usually exit Python interpreter in the process) | False |
| callback | func |
optional callback if the resource limit is exceeded | None |
| verbose | bool | detail to print out (default: if exceeded; True: every step; False: no output) | None |
Examples:
# Using with-as:
with sc.resourcemonitor(mem=0.8) as resmon:
memory_heavy_job()
# As a standalone (don't forget to call stop!)
resmon = sc.resourcemonitor(mem=0.95, cpu=0.9, time=3600, label='Load checker', die=False, callback=post_to_slack)
long_cpu_heavy_job()
resmon.stop()
print(resmon.to_df())
,Methods
| Name | Description |
|---|---|
| check | Check if any limits have been exceeded |
| kill | Kill all processes |
| monitor | Actually run the resource monitor |
| start | Start the monitor running |
| stop | Stop the monitor from running |
| to_df | Convert the log into a pandas dataframe |
check
sc_profiling.resourcemonitor.check()Check if any limits have been exceeded
kill
sc_profiling.resourcemonitor.kill()Kill all processes
monitor
sc_profiling.resourcemonitor.monitor(label=None, *args, **kwargs)Actually run the resource monitor
start
sc_profiling.resourcemonitor.start(label=None)Start the monitor running
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| label | str | optional label for printing progress | None |
stop
sc_profiling.resourcemonitor.stop()Stop the monitor from running
to_df
sc_profiling.resourcemonitor.to_df()Convert the log into a pandas dataframe
tracecalls
sc_profiling.tracecalls(
trace='<default>',
exclude='<default>',
regex=False,
repeats=False,
custom=None,
verbose=None,
)Trace all function calls.
Alias to sys.steprofile().
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| trace | str / list / regex |
the module(s)/file(s) to trace calls from (’’ matches all, but this is usually undesirable) | '<default>' |
| exclude | str / list / regex |
a list of modules/files to exclude (default excludes builtins; set to None to not exclude anything) | '<default>' |
| regex | bool | whether to interpret trace and exclude as regexes rather than simple string matching | False |
| repeats | bool | whether to record repeat calls of the same function (default False) | False |
| custom | func |
if provided, use this rather than the built in logic for checking for matches | None |
| verbose | bool | how much information to print (False=silent, None=default, True=debug) | None |
Examples:
import mymodule as mm
# In context block
with sc.tracecalls('mymodule'):
mm.big_operation()
# Explicitly
tc = sc.tracecalls('*mysubmodule*', exclude='^init*', regex=True, repeats=True)
tc.start()
mm.big_operation()
tc.stop()
tc.df.disp()- New in version 3.2.0.
- New in version 3.2.1: “custom” argument added; “kwargs” removed
Methods
| Name | Description |
|---|---|
| check_expected | Check function calls against a list of expected function calls. |
| disp | Display the results |
| start | Start profiling |
| stop | Stop profiling |
| to_df | Convert to a dataframe; if repeats=True, also count repeats |
check_expected
sc_profiling.tracecalls.check_expected(expected, die=False)Check function calls against a list of expected function calls.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| expected | set / list / any | if a list of set of strings, check those function names; if object(s) or classes are supplied, check each method | required |
| die | bool | raise an exception if any expected function calls were not called | False |
Example:
# Check which methods of a class are called
with sc.tracecalls() as tc:
my_obj = MyObj()
my_obj.run()
expected = tc.check_expected(MyObj) # Equivalent to tc.check_expected(my_obj)
print(expected)disp
sc_profiling.tracecalls.disp(maxlen=60)Display the results
start
sc_profiling.tracecalls.start()Start profiling
stop
sc_profiling.tracecalls.stop(disp=None)Stop profiling
to_df
sc_profiling.tracecalls.to_df()Convert to a dataframe; if repeats=True, also count repeats
Functions
| Name | Description |
|---|---|
| benchmark | Benchmark Python performance |
| checkmem | Checks how much memory the variable or variables in question use by dumping |
| checkram | Measure actual memory usage, typically at different points throughout execution. |
| listfuncs | Enumerate all functions in the supplied arguments; used in sc.profile(). |
| mprofile | Profile the line-by-line memory required by a function. See profile() for a |
benchmark
sc_profiling.benchmark(
repeats=5,
scale=1,
verbose=False,
which='python, numpy',
parallel=False,
return_timers=False,
)Benchmark Python performance
Performs a set of standard operations in both Python and Numpy and times how long they take. Results are returned in terms of millions of operations per second (MOPS). With default settings, this function should take very approximately 0.1 s to run (depending on the machine, of course!).
For Python, these operations are: for loops, list append/indexing, dict set/get, and arithmetic. For Numpy, these operations are: random floats, random ints, addition, and multiplication.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| repeats | int | the number of times to repeat each test | 5 |
| scale | float | the scale factor to use for the size of the loops/arrays (default 1e3 for Python and 1e6 for NumPy) | 1 |
| verbose | bool | print out the results after each repeat | False |
| which | str | whether to run Python tests, Numpy tests, or both (default) | 'python, numpy' |
| parallel | bool / int | whether to run the tests across all cores | False |
| return_timers | bool | if True, return the timer objects instead of the “MOPS” results | False |
Returns
| Name | Type | Description |
|---|---|---|
| A dict with keys “python” and “numpy” for the number of MOPS for each |
Examples:
sc.benchmark() # Returns e.g. {'python': 11.43, 'numpy': 236.595}
numpy_mops = sc.benchmark(which='numpy')
if numpy_mops < 100:
print('Your computer is slow')
elif numpy_mops > 400:
print('Your computer is fast')
else:
print('Your computer is normal')
sc.benchmark(parallel=True) # Use all CPUs- New in version 3.0.0.
- New in version 3.1.0: “parallel” argument; increased default scale
- New in version 3.2.4: replaced “python” and “numpy” arguments with “which”
checkmem
sc_profiling.checkmem(
var,
descend=1,
order='size',
compresslevel=0,
maxitems=1000,
subtotals=True,
plot=False,
verbose=False,
**kwargs,
)Checks how much memory the variable or variables in question use by dumping them to file.
Note on the different functions:
- `sc.memload()` checks current total system memory consumption
- `sc.checkram()` checks RAM (virtual memory) used by the current Python process
- `sc.checkmem()` checks memory consumption by a given object
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| var | any | the variable being checked | required |
| descend | bool | whether or not to descend one level into the object | 1 |
| order | str | order in which to list items: “size” (default), “alphabetical”, or “none” | 'size' |
| compresslevel | int | level of compression to use when saving to file (typically 0) | 0 |
| maxitems | int | the maximum number of separate entries to check the size of | 1000 |
| subtotals | bool | whether to include subtotals for different levels of depth | True |
| plot | bool | if descending, show the results as a pie chart | False |
| verbose | bool or int | detail to print, if >1, print repr of objects along the way | False |
| **kwargs | dict | passed to sc.load() |
{} |
Examples:
import numpy as np
import sciris as sc
list_obj = ['label', np.random.rand(2483,589)])
sc.checkmem(list_obj)
nested_dict = dict(
foo = dict(
a = np.random.rand(5,10),
b = np.random.rand(5,20),
c = np.random.rand(5,50),
),
bar = [
np.random.rand(5,100),
np.random.rand(5,200),
np.random.rand(5,500),
],
cat = np.random.rand(5,10),
)
sc.checkmem(nested_dict)New in version 3.0.0: descend multiple levels; dataframe output; “alphabetical” renamed “order”
checkram
sc_profiling.checkram(unit='mb', fmt='0.2f', start=0, to_string=True)Measure actual memory usage, typically at different points throughout execution.
Note on the different functions:
- `sc.memload()` checks current total system memory consumption
- `sc.checkram()` checks RAM (virtual memory) used by the current Python process
- `sc.checkmem()` checks memory consumption by a given object
Example:
import sciris as sc
import numpy as np
start = sc.checkram(to_string=False)
a = np.random.random((1_000, 10_000))
print(sc.checkram(start=start))New in version 1.0.0.
listfuncs
sc_profiling.listfuncs(
*args,
private='__init__',
include=None,
exclude=None,
strict=False,
)Enumerate all functions in the supplied arguments; used in sc.profile().
If module(s) are supplied, recursively search them for functions and classes. If class(es) are supplied, search them for methods. Otherwise, search input(s) for functions.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| args | list | the arguments to parse for functions; can be modules, classes, or functions. | () |
| private | bool / str / list | if True and a class is supplied, follow private functions; if a string/list, follow only those private functions (default '__init__') |
'__init__' |
| include | str | if a class/module is supplied, include only functions matching this string | None |
| exclude | str | if a class/module is supplied, exclude functions matching this string | None |
| strict | bool | if True, raise an exception if something is not a function, rather than recurse into it | False |
- New in version 3.2.2.
- New in version 3.2.4: “strict” argument
mprofile
sc_profiling.mprofile(run, follow=None, show_results=True, *args, **kwargs)Profile the line-by-line memory required by a function. See profile() for a usage example.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| run | function |
The function to be run | required |
| follow | function |
The function or list of functions to be followed in the profiler; if None, defaults to the run function | None |
| show_results | bool | whether to print the statistics of the profile to stdout | True |
| args, kwargs | Passed to the function to be run | required |
Returns
| Name | Type | Description |
|---|---|---|
| LineProfiler (by default, the profile output is also printed to stdout) |