Profiling regions
Install for this page:
pip install scope-profiler(base install only).
A region is the fundamental unit of measurement in scope-profiler. Every time a region is entered and exited, the profiler records a start/end timestamp pair and increments the call counter.
Regions only record while profiling is configured, so the cells on this page share one profiling session, started here and written at the end. The output goes to the scratch directory.
run = ProfileManager.setup(file_path="regions.h5")Creating regions
Section titled “Creating regions”Decorator — @ProfileManager.profile
Section titled “Decorator — @ProfileManager.profile”Ideal for wrapping entire functions:
@ProfileManager.profile("solver")def solve(A, b): return np.linalg.solve(A, b)
A, b = np.eye(3), np.ones(3)print(solve(A, b))print(ProfileManager.get_region("solver").num_calls)[1. 1. 1.]1Without an explicit name the function name is used:
@ProfileManager.profiledef solve(A, b): return np.linalg.solve(A, b)
# region name: "solve"print(solve(A, b))print("solve" in ProfileManager.recorded_regions())[1. 1. 1.]TrueYou can recursively profile nested Python function calls from a decorated entrypoint:
def run_pipeline(A, b): return sum(b)
@ProfileManager.profile("solve", recursive=True)def solve(A, b): return run_pipeline(A, b)
print(solve(A, b))3.0Nested calls are stored as additional regions using fully-qualified names
such as my_module.run_pipeline.
[name for name in ProfileManager.recorded_regions() if "run_pipeline" in name]['__main__.run_pipeline']Context manager — ProfileManager.region()
Section titled “Context manager — ProfileManager.region()”Ideal for profiling a section of code inside a function:
def compute_rhs(state): return 2.0 * state
def time_step(state, dt): with ProfileManager.region("rhs_evaluation"): rhs = compute_rhs(state)
with ProfileManager.region("state_update"): state += dt * rhs
time_step(np.ones(3), 0.1)profile_region() is the original name for this method and still works —
the two are the same object, so existing instrumentation needs no change.
# Bound methods are created on access, so compare the underlying functions.ProfileManager.profile_region.__func__ is ProfileManager.region.__func__TrueMixing both
Section titled “Mixing both”Decorators and context managers can be mixed freely. They share the same region registry, so using the same name in both places accumulates into a single region:
def process(chunk): time.sleep(0.001)
@ProfileManager.profile("compute")def compute_batch(data): for chunk in data: with ProfileManager.region("compute"): process(chunk)
# Both the decorator and the context manager contribute# to the same "compute" region.compute_batch([1, 2, 3])print(ProfileManager.get_region("compute").num_calls)4One call of the decorated function plus three entries of the context manager
make four calls of the single compute region.
Region identity
Section titled “Region identity”Regions are identified by name (a string). The first call to
region("foo") creates the region; subsequent calls with the same name return
the existing instance. This means:
- The same region can be entered from multiple call sites.
- Call counts and timestamps accumulate across all sites.
- Regions are scoped to the current
ProfileManager.setup()session. Callingsetup()again clears all regions.
ProfileManager.region("foo") is ProfileManager.region("foo")TrueNesting
Section titled “Nesting”Regions can be nested arbitrarily:
def work(): time.sleep(0.001)
with ProfileManager.region("outer"): with ProfileManager.region("inner"): work()Each region independently records its own timestamps. The library does not compute parent-child relationships — that can be done in post-processing by comparing the recorded intervals.
Accessing region data at runtime
Section titled “Accessing region data at runtime”Before calling finalize(), you can inspect in-memory data:
region = ProfileManager.get_region("solver")
# Number of completed callsprint(region.num_calls)
# Durations of buffered (not yet flushed) calls, in nanosecondsdurations = region.get_durations_numpy()print(durations.shape, durations.dtype)1(1,) int64All registered regions:
for name, region in ProfileManager.get_all_regions().items(): print(f"{name}: {region.num_calls} calls")solver: 1 callssolve: 2 calls__main__.run_pipeline: 1 callsrhs_evaluation: 1 callsstate_update: 1 callscompute: 4 callsfoo: 0 callsouter: 1 callsinner: 1 callsFinalizing writes the file. A following setup() starts a new session, which
clears all regions:
ProfileManager.finalize(verbose=False)ProfileManager.setup(file_path="regions.h5")print(ProfileManager.recorded_regions())ProfileManager.finalize(verbose=False)()