# How to Use the Profiler and Timer for Performance Measurement in OpenMed

> Learn to use OpenMed's Profiler and Timer for precise performance measurement. Optimize your code with nanosecond accuracy using these lightweight, zero-overhead tools.

- Repository: [Maziyar Panahi/openmed](https://github.com/maziyarpanahi/openmed)
- Tags: performance
- Published: 2026-06-11

---

**OpenMed provides lightweight, zero-overhead performance measurement tools in [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py), including a contextual `Profiler` for aggregated timing reports and a simple `Timer` for ad-hoc measurements, both utilizing `time.perf_counter()` for nanosecond precision.**

OpenMed includes a sophisticated yet easy-to-use profiling toolkit designed to help developers identify bottlenecks in medical ML pipelines. The [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) module offers two complementary approaches: a stateful **Profiler** that aggregates multiple timing blocks into detailed reports, and a lightweight **Timer** for single-shot measurements. These utilities are implemented in pure Python and can be enabled or disabled globally without code changes, making them ideal for both development debugging and production monitoring.

## Profiler Architecture and Core Components

The `Profiler` class is the primary instrument for collecting performance metrics across complex workflows. According to the source code in [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py), the architecture relies on three main abstractions that work together to provide zero-overhead profiling when disabled.

### TimingResult and ProfileReport

At the foundation, the **`TimingResult`** class (defined at lines 21-32) stores individual measurements including the operation name, duration, and optional metadata dictionary. These results are aggregated by the **`ProfileReport`** class (lines 39-84), which provides helper methods like `total_duration()`, `summary()`, and `format_report()` to analyze collected timings.

### The Profiler Class and measure() Context Manager

The **`Profiler`** class definition begins at line 122 and serves as the main entry point for performance sessions. It provides the **`measure()`** method (implemented as a context manager around lines 60-86) that records elapsed time for wrapped code blocks. When enabled, `measure()` creates a `TimingResult` for each block; when disabled, it yields control immediately without recording, ensuring zero runtime overhead.

### Global Profiler State Management

For library-wide instrumentation without passing instances, the module provides global helpers (lines 233-292): **`get_profiler()`**, **`enable_profiling()`**, **`disable_profiling()`**, and the **`@profile`** decorator. These functions manage a singleton `_global_profiler` variable, enabling thread-safe access from any module without explicit object passing.

## Timer Implementation and Usage

For simple, single-shot measurements, OpenMed provides the **`Timer`** class starting at line 405. Unlike the stateful Profiler, Timer operates as a minimalistic stopwatch with `start()`, `stop()`, `elapsed_ms`, and `elapsed_s` properties.

### Direct Timer Usage

You can instantiate and control a Timer manually for precise measurement of specific code sections. This is useful when you need the elapsed time as a return value rather than an aggregated report.

### The timed Context Manager

The **`timed`** context manager (defined at line 554) offers a convenience wrapper around the Timer class. It automatically starts timing on entry, stops on exit, and logs the duration at the DEBUG level, making it ideal for quick diagnostic logging without report generation.

## Practical Implementation Examples

The following examples demonstrate common patterns for integrating OpenMed's performance tools into your codebase.

### Basic Profiling with Explicit Profiler Instances

Create a dedicated `Profiler` instance when you need isolated measurement sessions for specific components:

```python
from openmed.utils import Profiler

# Initialize profiler (enabled=True by default)

profiler = Profiler(enabled=True)

# Measure model loading

with profiler.measure("load_model"):
    model = load_my_model()

# Measure inference

with profiler.measure("inference"):
    result = model.predict(data)

# Generate formatted report

print(profiler.report().format_report())

```

This produces a structured output showing total duration and percentage breakdowns for each named operation.

### Using the Global Profiler Across Modules

For application-wide profiling, enable the global singleton and access it from any function:

```python
from openmed.utils import enable_profiling, get_profiler

# Enable once at application startup

enable_profiling()

def process_batch(data):
    with get_profiler().measure("batch_processing"):
        return expensive_transform(data)

# Called from anywhere in your codebase

process_batch(x)
process_batch(y)

# Retrieve aggregated report

report = get_profiler().report()
print(report.format_report(include_metadata=True))

```

### Decorating Functions with @profile

The `@profile` decorator provides non-intrusive instrumentation for existing functions:

```python
from openmed.utils import profile, enable_profiling

enable_profiling()

@profile("data_preprocessing")
def clean_data(raw):
    return heavy_cleaning_operation(raw)

clean_data(dataset)
print(get_profiler().report().format_report())

```

The decorator automatically wraps the function with the global profiler's `measure()` context, recording timings under the specified name.

### Single-Shot Timing with Timer

For quick measurements without report aggregation:

```python
from openmed.utils import Timer, timed

# Direct usage

timer = Timer().start()
run_algorithm()
milliseconds = timer.stop()
print(f"Algorithm took {milliseconds:.2f} ms")

# Context-manager with automatic logging

with timed("database_query"):
    results = db.fetch_large_table()

```

### Combining Profiler and Timer

You can nest Timer measurements inside Profiler blocks for granular diagnostics:

```python
from openmed.utils import Profiler, timed

profiler = Profiler(enabled=True)

with profiler.measure("end_to_end"):
    with timed("initialization"):
        setup_environment()
    with timed("execution"):
        run_main_logic()

print(profiler.report().format_report())

```

Here, the Profiler captures the total `end_to_end` duration while the `timed` context managers emit debug logs for sub-steps.

## Summary

- **OpenMed's profiling toolkit** resides in [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) and provides both aggregated and single-shot timing utilities.
- The **`Profiler`** class uses `measure()` as a context manager to collect `TimingResult` objects into `ProfileReport` summaries, with global singleton access via `get_profiler()`.
- The **`Timer`** class and **`timed`** context manager offer lightweight alternatives for ad-hoc measurements using `time.perf_counter()`.
- **Zero-overhead operation** is achieved by setting `enabled=False` or calling `disable_profiling()`, which causes context managers to skip recording entirely.
- Source file locations include [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) (implementation), [`tests/unit/test_profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/tests/unit/test_profiling.py) (validation), and [`openmed/__init__.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/__init__.py) (top-level exports).

## Frequently Asked Questions

### How do I disable profiling in production without removing instrumentation code?

Call **`disable_profiling()`** at application startup or ensure the `Profiler` is initialized with `enabled=False`. When disabled, the `measure()` context manager yields immediately without recording, and the `@profile` decorator executes the wrapped function directly. This design ensures zero runtime overhead in production environments while preserving instrumentation for development.

### What is the difference between the Profiler and Timer classes?

The **`Profiler`** (line 122) maintains state across multiple measurements, aggregating them into a `ProfileReport` with statistical summaries and percentage breakdowns. The **`Timer`** (line 405) is a minimal stopwatch for single measurements that returns raw duration values without persistence. Use Profiler for benchmarking complex workflows; use Timer for simple, one-off timing checks or logging.

### Can I attach custom metadata to specific timing measurements?

Yes. The `Profiler.measure()` method accepts an optional `metadata` parameter as a dictionary. When provided, this data is stored in the `TimingResult` object and can be rendered in reports via `format_report(include_metadata=True)`. This is useful for tracking parameters like batch sizes or input shapes alongside timing data.

### Is the global profiler thread-safe for multi-threaded applications?

The global profiler singleton stored in `_global_profiler` is designed for thread-safe access. The `enable_profiling()` and `disable_profiling()` functions operate atomically, and the `get_profiler()` function returns the same instance across threads. However, individual `TimingResult` collections are not automatically thread-local; concurrent measurements from different threads will aggregate into the same report, which is typically desirable for total application profiling.