PyO3 / PyO3/pyo3

`#[pyfunction]` gets `METH_STATIC` since 0.28, which makes calls slower on CPython 3.11+

Open
#6,426 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
16.2k
Forks
1k
Avg merge
2d 6h
Merged PRs (30d)
66

Description

Bug Description

Since PyO3 0.28, every module-level #[pyfunction] is created with the METH_STATIC flag. A function with one parameter has ml_flags 0xa2 (METH_FASTCALL | METH_KEYWORDS | METH_STATIC). With PyO3 0.27.2 it has 0x82.

The flag has two effects:

  • function.__self__ is None. With 0.27.2 it is the module, as it is for CPython's own builtin functions such as math.sqrt.
  • On CPython 3.11 and newer, a call of a function that has parameters and no **kwargs is about 2 to 4 ns slower. For a function with an empty body this is 10 to 21%. Functions without parameters and functions with **kwargs are not slower. CPython 3.9 and 3.10 are not affected.

I found this while benchmarking fastcrc after upgrading PyO3 from 0.26 to 0.29.2.

Steps to Reproduce

A reproduction is at https://github.com/overcat/pyo3-meth-static-repro. It defines pairs of functions with empty bodies. One function of each pair is a plain #[pyfunction]. The other one uses pass_module, which does not get the flag, and is the control.

git clone https://github.com/overcat/pyo3-meth-static-repro
cd pyo3-meth-static-repro
python -m venv .venv
.venv/bin/pip install .
.venv/bin/python repro.py

Output with CPython 3.14.6 and PyO3 0.29.2:

               plain #[pyfunction]             #[pyfunction(pass_module)]
               ml_flags  __self__  ns/call     ml_flags  __self__  ns/call     difference
no parameters  0x0024    None         13.1     0x0004    module       13.0      +0.1 ns   +1%
one parameter  0x00a2    None         19.6     0x0082    module       16.4      +3.3 ns  +20%
**kwargs       0x0023    None         16.8     0x0003    module       16.7      +0.1 ns   +1%

0x0020 = METH_STATIC

With PyO3 0.27.2, both columns show the same flags, __self__ is the module in both, and the differences are 0.0 ns.

Your operating system and version

Linux Mint 22.3 (kernel 7.0.0-31-generic, x86-64)

Your Python version (python --version)

Python 3.14.6

Your Rust version (rustc --version)

rustc 1.98.0 (88d9e12ae 2026-08-18)

Your PyO3 version

0.29.2 (also 0.28.2 and main at cb456da1; 0.27.2 is not affected)

How did you install python? Did you use a virtualenv?

mise (a precompiled python-build-standalone build), in a virtualenv created with "python -m venv"

Additional Info

This was introduced by #5581. The README of the reproduction explains the cause in PyO3 and in CPython. It also has the results for several PyO3 and CPython versions, the instruction counts and a workaround.

There is a candidate fix with a test: https://github.com/overcat/pyo3/commit/b3d1fd3277ee15d25943593aaab2cf17d20e7198. If you agree with the approach, I can turn it into a PR.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the change introduced by #5581 and the candidate fix commit b3d1fd3277ee15d25943593aaab2cf17d20e7198. Run the pyo3-meth-static-repro benchmark across the affected PyO3 and CPython versions, then verify the candidate test preserves module binding and avoids the regression for parameterized functions.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.