GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Restore (or beat) Python 2 performance for arithmetic operations on ints that fit into a single word · Issue #101291 · python/cpython

Restore (or beat) Python 2 performance for arithmetic operations on ints that fit into a single word #101291

New issue
Copy link
New issue
Copy link
Closed
Closed
Restore (or beat) Python 2 performance for arithmetic operations on ints that fit into a single word#101291
Copy link
Labels
interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usagetriagedThe issue has been accepted as valid by a triager.The issue has been accepted as valid by a triager.

Description

@markshannon
markshannon
opened on Jan 24, 2023
Issue body actions

In Python 2 ints and longs were different objects, and the design of each was tailored to the different size and use cases.
In Python3 we dropped the distinction, but we also dropped the design for ints that fit into a single word.
We have added various fast paths for "medium" integers (e.g. #89109) but the underlying data structure gets in the way.

We should layout the int/long object so that it supports fast operations for most integers.

See faster-cpython/ideas#548 for a fuller discussion

Linked PRs

  • GH-101291: Refactor the PyLongObject struct #101292
  • GH-101291: Low level opt-in API for pylong #101685
  • GH-101291: Rearrange the size bits in PyLongObject #102464
  • GH-101291: Avoid using macros with casts in low-level long API. #104742
  • [3.12] GH-101291: Avoid using macros with casts in low-level long API. (GH-104742) #104759
  • GH-101291: Add warning to "what's new" that PyLongObject internals have changed. #107388
  • [3.12] GH-101291: Add warning to "what's new" that PyLongObject internals have changed. (GH-107388) #107392
  • gh-101291: Add versionadded directives for PyUnstable_Long_* #125384
  • [3.13] gh-101291: Add versionadded directives for PyUnstable_Long_* (GH-125384) #125407
  • [3.12] gh-101291: Add versionadded directives for PyUnstable_Long_* (GH-125384) #125408
Reacted by Raymond Hettinger and Vladimir
Reacted by Bruno Cabral

Activity

Next
  1. markshannon
    added
    performancePerformance or resource usagePerformance or resource usage
    on Jan 24, 2023
  2. markshannon
    added a commit that references this issue on Jan 30, 2023

    GH-101291: Refactor the `PyLongObject` struct into object header and …

    c1b1f51
  3. mdboom
    added a commit that references this issue on Jan 31, 2023

    pythonGH-101291: Refactor the `PyLongObject` struct into object heade…

    4874c33
  4. markshannon
    added a commit that references this issue on Mar 22, 2023

    GH-101291: Rearrange the size bits in PyLongObject (GH-102464)

    7559f5f
  5. Fidget-Spinner
    added a commit that references this issue on Mar 27, 2023

    pythonGH-101291: Rearrange the size bits in PyLongObject (pythonGH-10…

    32f8276
  6. warsaw
    added a commit that references this issue on Apr 11, 2023

    pythonGH-101291: Rearrange the size bits in PyLongObject (pythonGH-10…

    3a91146
  7. markshannon
    added a commit that references this issue on May 21, 2023

    GH-101291: Add low level, unstable API for pylong (GH-101685)

    9392379
  8. iritkatriel
    added a commit that references this issue on May 22, 2023

    GH-101291: Avoid using macros with casts in low-level long API. (#104742

    e295d86
  9. miss-islington
    added a commit that references this issue on May 22, 2023

    pythonGH-101291: Avoid using macros with casts in low-level long API. (…

    fd90299
  10. JelleZijlstra
    added a commit that references this issue on May 23, 2023

    [3.12] GH-101291: Avoid using macros with casts in low-level long API. (

    e43fbbd
  11. Yhg1s commented on Jul 6, 2023

    @Yhg1s
    Yhg1s
    on Jul 6, 2023
    Member
    More actions

    Has there been any progress in documenting the changes made in Python 3.12? (#101292 (comment))

  12. gvanrossum commented on Jul 7, 2023

    @gvanrossum
    gvanrossum
    on Jul 7, 2023
    Member
    More actions

    Maybe @markshannon can answer that? AFAICT all the commits linked above are his. I know we had someone who was interested in pursuing this further but she had to bow out.

  13. Yhg1s commented on Jul 20, 2023

    @Yhg1s
    Yhg1s
    on Jul 20, 2023
    Member
    More actions

    @markshannon Where are we with documentation for this? If it's not documented, do we need to start working to roll this back? I'm not comfortable with this change in rc1 if it's not documented.

  14. gvanrossum commented on Jul 20, 2023

    @gvanrossum
    gvanrossum
    on Jul 20, 2023
    Member
    More actions

    Mark is at EuroPython. If you are there too you can talk to him. We will get it documented.

  15. gvanrossum commented on Jul 21, 2023

    @gvanrossum
    gvanrossum
    on Jul 21, 2023
    Member
    More actions

    I did a little research. It looks like there are two key changes. First, struct _longobject (defined in Include/cpython/longintrepr.h but considered an internal implementation detail) has changed. It used to be

    struct _longobject {
        PyObject_VAR_HEAD
        digit ob_digit[1];
    };
    

    I.e., there was an array ob_digit whose length was abs(ob_size), where sign(ob_size) gave the sign of the overall value.

    The new (still internal) representation is as follows:

    typedef struct _PyLongValue {
        uintptr_t lv_tag; /* Number of digits, sign and flags */
        digit ob_digit[1];
    } _PyLongValue;
    
    struct _longobject {
        PyObject_HEAD
        _PyLongValue long_value;
    };
    

    and there are new internal macros to determine the number of digits and the sign, and a bunch of internal macros to handle "compact" values (which fit in 1-2 "digits").

    There are two new public, unstable APIs to support the concept of "compact" values: PyUnstable_Long_IsCompact and PyUnstable_Long_CompactValue. See https://docs.python.org/3.12/c-api/long.html#c.PyUnstable_Long_IsCompact. (In reality these are implemented as macros, and not intended to be part of any ABI.) Everything else that digs through the internals is defined in Include/internal/pycore_long.h, and requires defining Py_BUILD_CORE.

    Details of what the bits in lv_tag mean are intentionally not published -- these are meant to be opaque. Applications that used to dig through ob_digits using ob_size as guidance will break, and have two options: Switch to calling the Python-level APIs int.to_bytes() and int.from_bytes() via PyObject_CallMethod() (see note at https://docs.python.org/3.12/c-api/long.html#c.PyLong_FromString). Or go hard-core, defining Py_BUILD_CORE and importing pycore_long.h. Or, I guess, an intermediate path is to use the new unstable public APIs for dealing with "compact" values and use the slower arbitrary-precision API for non-compact values.

    I think in the What's New in 3.12 we should at least mention the change in the struct (calling out that using ob_size and ob_digits is no longer supported) and the new unstable public APIs (and what they're for). I don't think we need to call out the hard-core option, but maybe a reminder about to_bytes() and from_bytes() would be useful (even though that's been in the docs at least since 3.10).

    @Yhg1s @markshannon What do you think of this? I volunteer to make a PR for what's new 3.12 along the lines of what I wrote above.

  16. 8 remaining items

  17. miss-islington
    added a commit that references this issue on Oct 13, 2024

    pythongh-101291: Add versionadded directives for PyUnstable_Long_* (p…

    bf6bc53
  18. vstinner
    added 2 commits that reference this issue on Oct 13, 2024

    [3.13] gh-101291: Add versionadded directives for PyUnstable_Long_* (G…

    cff627a

    [3.12] gh-101291: Add versionadded directives for PyUnstable_Long_* (G…

    243a8a9
  19. StanFromIreland commented on Oct 29, 2026

    @StanFromIreland
    StanFromIreland
    on Oct 29, 2026
    Member
    More actions

    Triage: Can this be closed as completed?

  20. StanFromIreland
    added
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Oct 29, 2026
  21. skirpichev commented on Oct 29, 2026

    @skirpichev
    skirpichev
    on Oct 29, 2026
    Member
    More actions

    Stan, do you have some benchmarks showing that py2 performance for small ints actually was beaten in some py3 version?

    I don't think this should be closed. Issue description and referenced issue from the faster-cpython/ideas suggest a different layout for "small" and "big" integers.

    Here my experiments with tagged integers, using same layout as the fmpz in the FLINT. I would expect more or less similar performance boost for CPython's integers, currently using only "big-int" representation internally.

  22. StanFromIreland commented on Oct 29, 2026

    @StanFromIreland
    StanFromIreland
    on Oct 29, 2026
    Member
    More actions

    I haven't done any checks, which is why I'm asking ;-) I see this issue has been inactive for two years now, so I was wondering if perhaps this was complete?

  23. skirpichev commented on Oct 29, 2026

    @skirpichev
    skirpichev
    on Oct 29, 2026
    Member
    More actions

    I haven't done any checks, which is why I'm asking ;-)

    Ok, then I think we don't have enough arguments to close this as completed.

    I see this issue has been inactive for two years now, so I was wondering if perhaps this was complete?

    I think it's something people kept in heads all time, see e.g. https://discuss.python.org/t/87950 (probably this fits two years range).

  24. skirpichev
    added
    triagedThe issue has been accepted as valid by a triager.The issue has been accepted as valid by a triager.
    and removed
    pendingThe issue will be closed if no feedback is providedThe issue will be closed if no feedback is provided
    on Oct 29, 2026
  25. gvanrossum commented on Oct 30, 2026

    @gvanrossum
    gvanrossum
    on Oct 30, 2026
    Member
    More actions

    I tried to get more certainty about this. My computer has an Apple M3 chip, and the system python2.7 binary is run in emulation mode (soon no longer supported). So I had Codex build a native 2.7 from the 2.7 tag, and ran a little benchmark that tests 30-bit and 31-bit addition/subtraction (attached below).

    The results show that the difference is negligible:

    $ python3.15 ~/bench_int.py ; echo ; ./python.exe ~/bench_int.py ; echo
    3.15.0 (v3.15.0:d220409a13b, Oct  9 2026, 08:17:44) [Clang 21.0.0 (clang-2100.1.1.101)]
    CPython; arm64; 64-bit pointers
    Best of 5; eight additions/subtractions per loop; lower is faster.
    Below 2**30            7.81 ns/op  (range 7.81-7.88; 2000000 iterations)
    Above 2**30           15.54 ns/op  (range 15.54-15.60; 1400000 iterations)
    
    2.7.18 (tags/2.7-dirty:8d21aa21f2c, Oct 29 2026, 17:13:19)  [GCC Apple LLVM 21.0.0 (clang-2100.3.34.2)]
    CPython; arm64; 64-bit pointers
    sys.maxint = 9223372036854775807
    Best of 5; eight additions/subtractions per loop; lower is faster.
    Below 2**30            7.91 ns/op  (range 7.91-7.97; 2000000 iterations)
    Above 2**30            7.93 ns/op  (range 7.93-8.05; 2000000 iterations)

    TBH, repeating this a few times shows up various slightly different outcomes; e.g. sometimes 3.15 sees 8.35 ns/op while 2.7 sees 7.86 (that's about the most extreme I've seen).

    My AI, while building the native 2.7 binary, told me: "Native arm64 build with -O3, native CPU tuning, PGO, and LTO. All 30 verification tests passed." (it continued to babble about missing modules like SSL and Tk; it fixed those but I don't think that could have affected the outcome). The 3.15 binary was 3.15.0 from the official installer. (I will go ahead and download 3.15.2 for the latest security fixes, but I doubt that makes a difference.)

    I think this implies the original goal of this issue has been addressed. Yes, we can make "short" int arithmetic faster still, but I don't see a pressing reason to do, especially not using tagged pointers, which in my experience cause a lot of trouble.

    bench_int.py

  26. skirpichev commented on Oct 30, 2026

    @skirpichev
    skirpichev
    on Oct 30, 2026
    Member
    More actions

    Indeed, I even see some speedup on simple benchmarks (sum and Collatz conjecture):

    $ ./cpython-zoo/cpy2.7/bin/python -m sysconfig|grep CONFIG_A
            CONFIG_ARGS = "'--enable-optimizations' '--prefix=/home/sk/cpython-zoo/cpy2.7'"
    $ python3.16 -m sysconfig|grep CONFIG_A
            CONFIG_ARGS = "'--enable-optimizations'"
    $ ./cpython-zoo/cpy2.7/bin/python -m timeit -r11 -s 'from a import c' 'c(97)'
    10000 loops, best of 11: 26.6 usec per loop
    $ ./cpython-zoo/cpy2.7/bin/python -m timeit -r11 -s 'from a import c' 'c(871)'
    10000 loops, best of 11: 40.5 usec per loop
    $ python3.16 -m timeit -r11 -s 'from a import c' 'c(97)'
    10000 loops, best of 11: 24 usec per loop
    $ python3.16 -m timeit -r11 -s 'from a import c' 'c(871)'
    10000 loops, best of 11: 36.1 usec per loop
    $ ./cpython-zoo/cpy2.7/bin/python -m timeit -r11 -s 'from a import s; xs=[i for i in range(100)]' 's(xs)'
    100000 loops, best of 11: 8.19 usec per loop
    $ ./cpython-zoo/cpy2.7/bin/python -m timeit -r11 -s 'from a import s; xs=[i for i in range(1000)]' 's(xs)'
    10000 loops, best of 11: 78.9 usec per loop
    $ python3.16 -m timeit -r11 -s 'from a import s; xs=[i for i in range(100)]' 's(xs)'
    50000 loops, best of 11: 6.09 usec per loop
    $ python3.16 -m timeit -r11 -s 'from a import s; xs=[i for i in range(1000)]' 's(xs)'
    5000 loops, best of 11: 64.4 usec per loop

    I think this implies the original goal of this issue has been addressed.

    Well, it says "restore (or beat)".

    we can make "short" int arithmetic faster still, but I don't see a pressing reason to do

    I don't see how this issue introduces a pressure. Just a very concrete proposal for improving "small ints" performance, that's still seems valid (we can beat py2 more!).

    especially not using tagged pointers, which in my experience cause a lot of trouble.

    Could you elaborate on this?

    I'm not sure if this can be implemented portable. Is this a kind of trouble you are talking about? This seems to be not a problem for FLINT and I believe same will be true for PEP 11 platforms.

  27. gvanrossum commented on Oct 30, 2026

    @gvanrossum
    gvanrossum
    on Oct 30, 2026
    Member
    More actions

    I didn't say the issue is putting pressure on (it's not); I meant that in general I don't think faster ints are a priority.

    I had a bad experience with tagged pointers 40+ years ago. The problem was that whenever a tagged pointer escaped to a place that expected only a regular pointer, there was a nasty crash.

    I know Mark's proposal is different in details, but the code is still C, and I still worry about tagged pointers escaping to unsuspecting extension modules.

  28. picnixz commented on Oct 30, 2026

    @picnixz
    picnixz
    on Oct 30, 2026
    Member
    More actions

    I would suggest that we close this specific issue if we addressed the "restore" performance and open a new one if we find new ways of doing it. No need to keep this one open.

    Reacted by Stan Ulbrych
  29. gvanrossum
    closed this as completedon Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usagePerformance or resource usagetriagedThe issue has been accepted as valid by a triager.The issue has been accepted as valid by a triager.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.