Repository navigation
Optimize int add/sub for wide exact ints #151289
Copy link
Copy link
Open
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usagetype-featureA feature request or enhancementA feature request or enhancement
Description
CPython has a fast path for compact integers in binary add/sub, but wide exact ints still go through the generic long arithmetic path even when both operands fit in int64_t.
This issue proposes adding a separate fast path for exact PyLong operands that fit in signed 64-bit integers, while preserving the existing compact-int path.
Suggested implementation:
- Keep the current compact-int specialization unchanged.
- Add a separate wide-int path for exact ints that fit in int64_t.
- Preserve current behavior for overflow, subclasses, and other non-exact-int cases.
Motivation:
- Improve performance for wide integer add/sub without affecting the common compact-int hot path.
- Avoid adding new opcodes in the compact-int path.
- Fit within the current interpreter and specialization structure.
Benchmark evidence:
- I prototyped this locally with a benchmark covering compact and wide add/sub cases.
- Wide cases improved substantially, while compact cases remained effectively flat.
- Representative interpreter-only results with JIT disabled:
- add_wide: about 25% faster
- sub_wide: about 35% faster
- add_compact/sub_compact: effectively unchanged
Benchmark script used locally:
"""Microbenchmark compact vs wide int add/sub with pyperf.
Use this with PYTHON_JIT=0 and -S if you want a stable interpreter-only run:
PYTHON_JIT=0 ./python.exe -S Tools/scripts/bench_wide_int_pyperf.py
"""
from __future__ import annotations
import pyperf
def bench_add_compact() -> int:
a = 1
b = 2
return a + b
def bench_add_wide() -> int:
a = 10_000_000_000
b = 1
return a + b
def bench_sub_compact() -> int:
a = 1
b = 2
return a - b
def bench_sub_wide() -> int:
a = 10_000_000_000
b = 1
return a - b
def main() -> None:
runner = pyperf.Runner()
runner.bench_func("add_compact", bench_add_compact)
runner.bench_func("add_wide", bench_add_wide)
runner.bench_func("sub_compact", bench_sub_compact)
runner.bench_func("sub_wide", bench_sub_wide)
if __name__ == "__main__":
main()Linked PRs
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usagetype-featureA feature request or enhancementA feature request or enhancement
Activity
KRRT7 commentedon Jun 10, 2026
came out of #150425 but since the JIT is a no go zone for the near future ¯_(ツ)_/¯
skirpichev commentedon Jun 11, 2026
I would prefer if you share results of the pyperformance on this. Micro-benchmarks in #150425 looks impressive, but the pyperformance seems to be not affected (or even shows worse results).
So far, I'm not sure that this optimization worth code complications.
Perhaps, it will be better if we first change PyLong internals to use tagged pointers, it was recently come again in this d.p.o thread:
https://discuss.python.org/t/pylongobject-just-use-gmp/106086/29
With this, we will have essentially only two code paths for machine-sized integers and for big integers. Just like now.
KRRT7 commentedon Jun 11, 2026
ah, my bad @skirpichev, I had the message drafted but it wasn't sent:
KRRT7 commentedon Jun 11, 2026
actually I was planning to follow up in the future with that, I believe we can leverage both implementations.
KRRT7 commentedon Jun 11, 2026
you should consider the benchmarks in #150425 separate and probably irrelevant for the next few months since that touches the tier 2 JIT
skirpichev commentedon Jun 11, 2026
Thanks, but as I said - micro-benchmarks are less interesting.
I'm not sure. If we will use separate representation for machine-sized integers (take look on FLINT's fmpz), I don't think we will have need in a third code path ("wide ints").
CC @tim-one
PYTHON_JIT=0 should be relevant, no?
pythongh-151289: specialize shrinking int subtraction
pythongh-151289: reduce subtract specialization uop churn