# Profiling OpenMed Performance: A Complete Guide to the Built-In Profiler

> Learn to profile OpenMed performance with its built-in profiler. Measure execution time with nanosecond precision and zero overhead. A complete guide to maximizing your application's efficiency.

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

---

**OpenMed provides a lightweight, zero-dependency profiling framework in [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) that uses `Timer`, `Profiler`, and the `@profile` decorator to measure execution time with nanosecond precision and zero overhead when disabled.**

OpenMed, the open-source medical NLP repository developed by maziyarpanahi, ships with sophisticated performance profiling utilities designed specifically for analyzing biomedical text processing pipelines. The profiling system centers on a global singleton pattern that allows developers to toggle instrumentation at runtime without modifying core application logic. All profiling classes reside in [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) and depend only on Python standard library modules including `time`, `contextlib`, and `dataclasses`.

## Core Profiling Data Structures

The foundation of OpenMed's profiling system rests on two primary data classes that capture and aggregate timing measurements.

### TimingResult Dataclass

Individual measurements are stored as **`TimingResult`** instances, defined at lines 21-27 of [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py). This dataclass records the operation name, duration in seconds, and optional metadata dictionary.

```python
@dataclass
class TimingResult:
    name: str
    duration: float                # seconds

    metadata: Optional[Dict[str, Any]] = None

```

### ProfileReport Aggregation

The **`ProfileReport`** class (lines 39-87) aggregates multiple `TimingResult` entries into a comprehensive summary. It provides computed properties including `total_duration`, `timing_count`, and formatted output methods such as `summary()` and `format_report()`.

## The Profiler Class Implementation

The **`Profiler`** class serves as the primary instrumentation engine, implementing context managers and manual entry APIs for flexible timing capture alongside the simpler **`Timer`** utility.

### Session Control Methods

Each `Profiler` instance maintains session-level metadata through **`start()`** and **`stop()`** methods. The `start()` method records the wall-clock time for the entire profiling session, while `stop()` calculates the overall duration and stores it in the report metadata.

### Measuring Code Blocks with measure()

The **`measure(name, metadata=None)`** context manager (implementation around lines 59-86) captures high-resolution timestamps before and after code block execution:

```python
with profiler.measure("model_load"):
    model = load_medical_ner_model()

```

When the context exits, the elapsed time is automatically converted to a `TimingResult` and appended to the internal `_timings` list.

### Manual Timing and Metadata APIs

For scenarios requiring external timing sources, the **`add_timing(name, duration, metadata)`** method allows manual insertion of pre-calculated durations. Additionally, **`add_metadata(key, value)`** attaches global key-value pairs to the final `ProfileReport`.

## Global Profiler Utilities

OpenMed exposes profiling functionality through a global singleton pattern that enables application-wide instrumentation without explicit instance passing.

### Enabling and Disabling Profiling

The **`get_profiler()`** function lazily initializes a disabled global instance. To activate instrumentation:

```python
from openmed.utils.profiling import enable_profiling, disable_profiling

profiler = enable_profiling()  # Creates enabled instance and calls start()

# ... application code ...

disable_profiling()            # Stops session and disables further timing

```

The **`enable_profiling()`** function replaces the global singleton with an enabled `Profiler` and immediately invokes `start()`, while **`disable_profiling()`** halts the session and prevents further timing overhead.

### The @profile Decorator

The **`@profile(name=None)`** decorator automatically wraps function execution in a `Profiler.measure` block. Applied to any function, it records entry-to-exit timing without cluttering the function body:

```python
from openmed.utils.profiling import profile

@profile("tokenize")
def tokenize_medical_text(text: str):
    return text.split()

```

Decorated functions automatically report their timing to the global profiler singleton upon completion.

## Practical Profiling Workflows

The following patterns demonstrate typical OpenMed performance profiling scenarios for medical NLP pipelines.

### Profiling Model Inference Pipelines

Wrap distinct pipeline stages (model loading, preprocessing, inference, postprocessing) using the context manager to identify latency bottlenecks:

```python
from openmed.utils.profiling import enable_profiling, get_profile_report

profiler = enable_profiling()

def run_inference(text: str):
    with profiler.measure("model_load"):
        model = load_my_model()
    
    with profiler.measure("preprocess"):
        tokens = tokenize(text)
    
    with profiler.measure("inference"):
        entities = model.predict(tokens)
    
    with profiler.measure("postprocess"):
        result = format_entities(entities)
    
    return result

run_inference("Patient has hypertension.")
print(get_profile_report().format_report(include_metadata=True))

```

### Automatic Function Instrumentation

For repetitive utility functions, use the decorator pattern to maintain clean calling code:

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

profiler = enable_profiling()

@profile("tokenize")
def tokenize(text: str):
    return text.split()

@profile("predict")
def predict(tokens):
    return ["ENTITY"] * len(tokens)

def pipeline(text: str):
    return predict(tokenize(text))

pipeline("The patient was prescribed ibuprofen.")
print(get_profile_report().format_report())

```

### Batch Processing Throughput Analysis

For large-scale benchmark dashboards, combine the profiler with the **`BatchMetrics`** dataclass (lines 335-388) to calculate items-per-second and characters-per-second metrics:

```python
from openmed.utils.profiling import enable_profiling, BatchMetrics, InferenceMetrics

profiler = enable_profiling()

def process_batch(texts):
    batch_metrics = BatchMetrics()
    
    for txt in texts:
        with profiler.measure("single_item"):
            metric = InferenceMetrics(
                text_length=len(txt),
                token_count=len(txt.split()),
                entity_count=5,
                inference_time_ms=30.0,
                preprocessing_time_ms=5.0,
                postprocessing_time_ms=2.0,
                total_time_ms=37.0,
            )
            batch_metrics.items.append(metric)
    
    batch_metrics.total_time_ms = sum(m.total_time_ms for m in batch_metrics.items)
    return batch_metrics.format_report()

print(process_batch([
    "Patient: John Doe. Diagnosis: Diabetes.",
    "No acute disease identified."
]))

```

## Summary

- **OpenMed's profiler** resides in [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) and requires no external dependencies, utilizing only Python's standard library.
- **Three instrumentation patterns** are available: direct `Timer` usage, the `Profiler.measure()` context manager, and the `@profile` decorator for automatic function wrapping.
- **Global singleton management** through `enable_profiling()` and `disable_profiling()` allows runtime toggling without code changes, ensuring zero overhead when profiling is inactive.
- **BatchMetrics and ProfileReport** classes provide formatted summaries including total duration, timing counts, and throughput metrics essential for optimizing medical NLP pipelines.
- **Unit tests** in [`tests/unit/test_profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/tests/unit/test_profiling.py) validate the profiler's behavior and report formatting for production reliability.

## Frequently Asked Questions

### How do I enable profiling in an OpenMed script?

Call `enable_profiling()` from [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py) at the start of your script. This function instantiates a global `Profiler`, marks it as enabled, and starts the session timer. You can then access this instance via `get_profiler()` or use the decorator/context manager APIs that automatically report to the global singleton.

### What is the performance overhead when profiling is disabled?

When profiling is disabled via `disable_profiling()` or before `enable_profiling()` is called, the framework incurs virtually zero runtime cost. The global singleton defaults to a disabled state, and the `measure` context manager checks the enabled flag before entering timing logic, ensuring production deployments remain unaffected.

### Can I profile specific functions without modifying their internals?

Yes. Apply the `@profile` decorator (imported from [`openmed/utils/profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/utils/profiling.py)) to any function definition. This decorator automatically wraps the function body in a timing measurement block using the global profiler, recording the function's execution duration without requiring manual context manager insertion at every call site.

### Where are the profiling utilities tested in the OpenMed repository?

Unit tests validating the profiler's behavior, report formatting, and decorator functionality are located in [`tests/unit/test_profiling.py`](https://github.com/maziyarpanahi/openmed/blob/main/tests/unit/test_profiling.py). These tests verify that `TimingResult` objects aggregate correctly, that `ProfileReport.format_report()` produces expected output strings, and that the global profiler singleton maintains proper state across enable/disable cycles.