# Scalene: Line-Level CPU and Memory Profiler


Scalene is a [sampling profiler](https://pydevtools.com/handbook/explanation/sampling-vs-deterministic-profilers.md) that reports CPU time and memory activity by source line and function. It runs inside the target process, combining timing estimates with allocation and copying measurements to distinguish expensive Python code from work performed by native libraries.

Unlike [py-spy](https://pydevtools.com/handbook/reference/py-spy.md) and [Austin](https://pydevtools.com/handbook/reference/austin.md), Scalene requires starting the program under the profiler. It suits local diagnosis when a source-level report is more useful than attaching to an existing production process. [Compare Python profilers](https://pydevtools.com/handbook/explanation/which-python-profiler-should-i-use.md) for the alternatives.

## Install alongside project dependencies

In an existing [uv](https://pydevtools.com/handbook/reference/uv.md) project:

```bash
uv add --dev scalene
```

The `--dev` flag marks Scalene as a {{< term "dev-dependency" "development dependency" >}}, keeping it out of production installs. uv adds Scalene to the development dependency group and installs it in the project's virtual environment, alongside the application's dependencies.

Scalene publishes packages for Linux, macOS, and Windows. Its package metadata requires Python 3.8 or newer, excluding Python 3.11.0; free-threaded builds have dedicated wheels for Python 3.13 and 3.14. Wheel availability and dependency compatibility vary by platform and interpreter. On Python 3.13t, the [release notes](https://github.com/plasma-umass/scalene/releases/tag/v2.4.0) require `pydantic-core<=2.46.5`; the [PyPI file list](https://pypi.org/project/scalene/#files) lists available wheels.

## Record and reopen a profile

For an existing program saved as `script.py`, run:

```bash
uv run scalene run script.py
```

`uv run` executes the command inside the project's virtual environment, ensuring all dependencies are installed first. Scalene runs the program, then saves its measurements to `scalene-profile.json` in the current directory. Workloads must run long enough to collect samples; an empty profile does not establish that the program has no hotspots.

Display the saved report in the terminal:

```bash
uv run scalene view --cli
```

The output annotates source lines with timing and memory columns. To export a report with its viewer assets embedded:

```bash
uv run scalene view --standalone
```

This writes `scalene-profile.html`, which opens locally without fetching the viewer's assets from the internet.

| Option | Effect |
|---|---|
| `run --cpu-only` | Collect CPU measurements without memory or GPU profiling |
| `run -o results.json` | Save measurements under a specified filename |
| `run --profile-all` | Include source outside the entry-point directory, including libraries |
| `view results.json` | Open a specified saved profile |

## Interpret the measurements

| Report | Meaning |
|---|---|
| Python, native, and system time | Estimated time attributed to Python execution, native code, and system activity |
| Memory | Sampled allocation activity, per-line memory growth, and memory trends |
| Copy volume | Bytes copied through observed copying operations, useful when data crosses Python/native boundaries |
| Await time | Wall-clock time attributed to the source line where a coroutine waits, with mean and peak concurrent waiters |
| Stack views | Combined Python/native call paths and a timeline for relating activity to the run |

Memory allocation measurements differ from resident memory, the physical pages currently held by the process. [Austin's memory mode](https://pydevtools.com/handbook/reference/austin.md) measures changes in residency; the two reports answer different questions.

Async waits measure latency, not CPU consumption. A line with substantial waiting time may be waiting on a database or another task rather than performing expensive computation.

GPU measurements depend on the backend and accounting available on the machine. The [project README](https://github.com/plasma-umass/scalene#gpu-profiling) documents NVIDIA GPU reporting; a utilization percentage is not a trace of individual GPU kernels. Per-process accounting and device-wide utilization must remain distinct when other processes share a GPU.

## Account for attribution limits

The Python/native split is an estimate. [Issue #428](https://github.com/plasma-umass/scalene/issues/428) reports misleading attribution in multithreaded code; the maintainer explains that worker threads use a different attribution method from the main thread. Validate a surprising split against the workload and available native-stack evidence before treating it as a reason to rewrite code.

Native stack capture is unavailable on Windows. On Apple M4 and newer chips with SME instructions, Scalene disables native stack capture to avoid a signal-handling problem that corrupts numerical state. CPU and line profiling use a separate sampling path on those machines, so the availability of line reports does not imply that native stack views are available.

Sampling does not count every call or allocation. Short operations may receive few samples, and profiling changes the workload's execution costs. Compare repeated runs of the same workload when evaluating an optimization.

## Request optional optimization suggestions

The local viewer works without a model provider. Its optional optimization controls send source text and measured line metrics to the selected provider, such as OpenAI, Amazon Bedrock, Azure, or a local model served through Ollama.

A local model keeps those requests on its configured endpoint; selecting a hosted provider sends them to that provider. Suggestions propose code changes, so correctness checks and another measured run are still needed to establish an improvement.
