> ## Documentation Index
> Fetch the complete documentation index at: https://codspeed.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing Benchmarks in Python

> Creating performance tests for `pytest` using `pytest-codspeed`

export const CIWorkflow = ({minimal = false, enableWorkflowDispatch = true, runsOn = "ubuntu-latest", highlight = [], mode, modes, submodules = false, preSteps = [], buildSteps = ["# ...", "# Setup your environment here:", "#  - Configure your Python/Rust/Node version", "#  - Install your dependencies", "#  - Build your benchmarks (if using a compiled language)", "# ..."], benchmarkCommand = ["<Insert your benchmark command here>"], jobName = "Run benchmarks", env = {}}) => {
  const modeList = modes || (mode ? [mode] : undefined);
  if (!modeList || modeList.length === 0) {
    throw new Error("mode or modes is required");
  }
  const indent = (lines, depth) => {
    const reindentedLines = lines.map(l => l.length === 0 ? l : (" ").repeat(depth) + l);
    return reindentedLines.join("\n");
  };
  const workflowDispatchSection = enableWorkflowDispatch ? "  # `workflow_dispatch` allows CodSpeed to trigger backtest\n" + "  # performance analysis in order to generate initial data.\n" + "  workflow_dispatch:\n" : "";
  let yaml = "";
  if (!minimal) {
    yaml += `
name: CodSpeed Benchmarks

on:
  push:
    branches:
      - "main" # or "master"
  pull_request:
`;
    yaml += workflowDispatchSection;
  }
  yaml += `
jobs:
  benchmarks:
    name: ${jobName}
    runs-on: ${runsOn}`;
  if (!minimal) {
    yaml += `
    permissions: # optional for public repositories
      contents: read # required for actions/checkout
      id-token: write # required for OIDC authentication with CodSpeed`;
  }
  if (preSteps.length > 0) yaml += "\n" + indent(preSteps, 4);
  yaml += `
    steps:
      - uses: actions/checkout@v5`;
  if (submodules) {
    const value = typeof submodules === "string" ? submodules : "true";
    yaml += `\n        with:\n          submodules: ${value}`;
  }
  yaml += "\n" + indent(buildSteps, 6);
  const modeValue = modeList.join(",");
  yaml += `
      - name: Run the benchmarks
        uses: CodSpeedHQ/action@v4
        with:
          mode: ${modeValue}`;
  if (benchmarkCommand.length > 0) {
    const indentedBenchCommand = benchmarkCommand.length > 1 ? benchmarkCommand[0] + "\n" + indent(benchmarkCommand.slice(1), 12) : benchmarkCommand;
    const runLine = indent(["run: "], 10) + indentedBenchCommand;
    yaml += `\n${runLine}`;
  }
  const envEntries = Object.entries(env);
  if (envEntries.length > 0) {
    const envLines = ["env:", ...envEntries.map(([k, v]) => `  ${k}: ${v}`)];
    yaml += "\n" + indent(envLines, 8);
  }
  return <CodeBlock language="yaml" highlight={JSON.stringify(highlight)} {...minimal || ({
    filename: ".github/workflows/codspeed.yml",
    icon: "github"
  })}>
      {yaml}
    </CodeBlock>;
};

export const PypiInstall = ({packages}) => {
  return <CodeGroup>
      <code className="language-sh" filename="uv">
      uv add --dev {packages}
      </code>

      <code className="language-sh" filename="poetry">
      poetry add -G dev {packages}
      </code>

      <code className="language-sh" filename="pipenv">
      pipenv install -d {packages}
      </code>

      <code className="language-sh" filename="pip">
      pip install {packages}
      </code>
    </CodeGroup>;
};

To integrate CodSpeed with your Python codebase, the simplest way is to
[`pytest-codspeed`](https://github.com/CodSpeedHQ/pytest-codspeed). This
extension will automatically enable the CodSpeed engine on your benchmarks and
allow reporting to CodSpeed.

<Tip>
  Creating benchmarks with `pytest-codspeed` is backward compatible with the
  `pytest-benchmark` API. So if you already have benchmarks written with it, you
  can start using CodSpeed right away!
</Tip>

## Installation

First, install `pytest-codspeed` as a development dependency:

<PypiInstall packages="pytest-codspeed" />

## Usage

### Creating benchmarks

In a nutshell, `pytest-codspeed` offers two approaches to create performance
benchmarks that integrate seamlessly with your existing test suite.

Use `@pytest.mark.benchmark` to measure entire test functions automatically:

```python highlight={4} theme={null}
import pytest
from statistics import median

@pytest.mark.benchmark
def test_median_performance():
    input = [1, 2, 3, 4, 5]
    output = sum(i**2 for i in input)
    assert output == 55
```

Since this measure the entire function, you might want to use the `benchmark`
fixture for precise control over what code gets measured:

```python highlight={4} theme={null}
def test_mean_performance(benchmark):
    data = [1, 2, 3, 4, 5]
    # Only the function call is measured
    result = benchmark(lambda: sum(i**2 for i in data))
    assert result == 55
```

Check out the full documentation for more details:

<Card title="pytest-codspeed documentation" icon="code" horizontal href="/reference/pytest-codspeed">
  Explore advanced features including pedantic mode, benchmark options, and
  configuration parameters for fine-tuned performance testing.
</Card>

<Tip>
  For an in-depth tutorial on `pytest-codspeed`, see the [How to Benchmark
  Python with pytest](/guides/how-to-benchmark-python-with-pytest) guide.
</Tip>

### Testing the benchmarks locally

If you want to run the benchmarks tests locally, you can use the `--codspeed`
pytest flag:

```sh theme={null}
$ pytest tests/ --codspeed
======================== test session starts =========================
platform linux -- Python 3.10.4, pytest-7.1.3, pluggy-1.0.0
codspeed: 1.0.4
NOTICE: codspeed is enabled, but no performance measurement will be
made since it's running in an unknown environment.
rootdir: /home/user/codspeed-test, configfile: pytest.ini
plugins: codspeed-1.0.4
collected 6 items

tests/test_iterative_fibo.py .                                  [ 16%]
tests/test_recursive_fibo.py ..                                 [ 50%]
tests/test_recursive_fibo_cached.py ...                         [100%]

========================= 6 benchmark tested =========================
========================= 6 passed in 0.02s  =========================
```

<Note>
  Running `pytest-codspeed` locally will not produce any performance reporting.
  It's only useful for making sure that your benchmarks are working as expected.
  If you want to get performance reporting, you should run the benchmarks in
  your CI.
</Note>

### Running the benchmarks in your CI

To generate performance reports, you need to run the benchmarks in your CI. This
allows CodSpeed to automatically run benchmarks and warn you about regressions
during development.

<Tip>
  If you want more details on how to configure the CodSpeed action, you can check
  out the [Continuous Reporting section](/integrations/ci).
</Tip>

Here is an example of a GitHub Actions workflow that runs the benchmarks and
reports the results to CodSpeed on every push to the `main` branch and every
pull request:

<CIWorkflow
  mode="simulation"
  buildSteps={[
"- uses: actions/setup-python@v6",
"  with:",
'    python-version: "3.13"',
"- name: Install dependencies",
"  run: pip install -r requirements.txt",
]}
  benchmarkCommand={["pytest tests/ --codspeed"]}
/>

## Recipes

### Usage with `uv`

Install `uv` as a development dependency:

```sh theme={null}
uv add --dev pytest-codspeed
```

Then add the following GitHub Actions workflow to run the benchmarks:

<CIWorkflow
  mode="simulation"
  buildSteps={[
"- name: Install uv",
"  uses: astral-sh/setup-uv@v7",
"- name: Set up Python",
"  uses: actions/setup-python@v6",
"  with:",
"    python-version-file: pyproject.toml",
"- name: Install dependencies",
"  run: uv sync --all-extras --dev",
]}
  benchmarkCommand={["uv run pytest tests/ --codspeed"]}
/>

<Note>
  Using `actions/setup-python` to install python and not `uv install` is
  critical for tracing to work properly.
</Note>

### Running benchmarks in parallel

If your benchmarks are taking too much time to run under the CodSpeed action,
you can run them in parallel to speed up the execution.

#### Running benchmarks in parallel CI jobs

To parallelize your benchmarks, you can use
[`pytest-test-groups`](https://github.com/mark-adams/pytest-test-groups), a
`pytest` plugin that allows you to split your benchmark execution across several
CI jobs.

Install `pytest-test-groups` as a development dependency:

<PypiInstall packages="pytest-test-groups" />

Update your CI workflow to run benchmarks shard by shard:

<CIWorkflow
  minimal
  mode="simulation"
  highlight={[5, 6, 7, 22]}
  preSteps={["strategy:", "  matrix:", "    shard: [1, 2, 3, 4, 5]"]}
  buildSteps={[
"- name: Install uv",
"  uses: astral-sh/setup-uv@v7",
"- name: Set up Python",
"  uses: actions/setup-python@v6",
"  with:",
"    python-version-file: pyproject.toml",
"- name: Install dependencies",
"  run: uv sync --all-extras --dev",
]}
  benchmarkCommand={[
"uv run pytest tests/ --codspeed --test-group=${{ matrix.shard }} --test-group-count=5",
]}
/>

<Warning>
  The shard number must starts at 1. If you run with a shard number of 0, **all
  the benchmarks** will be run.
</Warning>

<Danger>
  **Same benchmark with different variations**

  For now, you cannot run the same benchmarks several times within the same run.
  If the same benchmark is run multiple times, you will receive the following
  comment on your pull request:

  <img src="https://mintcdn.com/codspeed/jKaxX6yy-Kzw1C-0/assets/parallel-benchmarks-variations-warning.png?fit=max&auto=format&n=jKaxX6yy-Kzw1C-0&q=85&s=f5e87d3fecc76f2fe3ce7f302e7f9a93" className="rounded-xl w-full max-w-lg mx-auto" alt="Multiple Benchmark Variations Error Message" width="1832" height="568" data-path="assets/parallel-benchmarks-variations-warning.png" />
</Danger>

Learn more about
[benchmark sharding and how to integrate with your CI provider](/features/sharded-benchmarks).

#### Running benchmarks in parallel processes

If you cannot split your benchmarks across multiple CI jobs, you can split them
across multiple processes in the same job. We only recommend this as an
alternative to the parallel CI jobs setup.

`pytest-codspeed` is compatible with
[`pytest-xdist`](https://pypi.org/project/pytest-xdist/), a `pytest` plugin
allowing to distribute the execution across multiple processes. You can simply
enable the `pytest-xdist` plugin on top of `pytest-codspeed`. This will allow
you to run your benchmarks in parallel using multiple processes.

First, install `pytest-xdist` as a development dependency:

<PypiInstall packages="pytest-xdist" />

Then, you can run your benchmarks in parallel with the `pytest-xdist` flag:

```sh theme={null}
pytest tests/ --codspeed -n auto
```

The change in the CI workflow would look like this:

```yaml .github/workflows/codspeed.yml {5} theme={null}
- name: Run benchmarks
  uses: CodSpeedHQ/action@v4
  with:
    mode: simulation
    run: pytest tests/ --codspeed -n auto
```

<Info>
  To combine measurement modes like simulation and memory, check out the
  documentation on [running multiple instruments
  serially](/integrations/ci/github-actions/configuration#running-multiple-instruments-serially).
</Info>

### Usage with Nox

It's possible to use `pytest-codspeed` with
[`Nox`](https://nox.thea.codes/en/stable/), a Python automation tool that allows
you to automate the execution of Python code across multiple environments.

Here is an example configuration file to run benchmarks with `pytest-codspeed`
using `Nox`:

```python noxfile.py theme={null}
import nox

@nox.session
def codspeed(session):
    session.install('pytest')
    session.install('pytest-codspeed')
    session.run('pytest', '--codspeed')
```

You can then run the benchmarks:

```sh theme={null}
nox --sessions codspeed
```

To use it with Github Actions, you can use the following workflow:

<CIWorkflow
  mode="simulation"
  buildSteps={[
"- uses: actions/setup-python@v6",
"  with:",
'    python-version: "3.13"',
"",
"- name: Install Nox",
"  run: pip install nox",
"",
"- name: Install dependencies",
"  run: nox --sessions codspeed --install-only",
]}
  benchmarkCommand={[
"nox --sessions codspeed --reuse-existing-virtualenvs --no-install",
]}
/>

Splitting the virtualenv installation and the execution of the benchmarks is
optional. Though this allows to speed up the execution of the benchmarks since
the dependencies will be installed or compiled without the CPU simulation
enabled.
