Skip to content

Remove private _PyLong_Zero and _PyLong_One variables #86327

Description

@vstinner
BPO 42161
Nosy @rhettinger, @mdickinson, @vstinner, @serhiy-storchaka, @corona10, @pablogsal, @miss-islington
PRs
  • bpo-42161: Add _PyLong_GetZero() and _PyLong_GetOne() #22993
  • bpo-42161: Use _PyLong_GetZero() and _PyLong_GetOne() #22995
  • bpo-42161: Use _PyLong_GetZero() and _PyLong_GetOne() #22998
  • bpo-42161: Remove private _PyLong_Zero and _PyLong_One #23003
  • bpo-42161: Micro-optimize _collections._count_elements() #23008
  • bpo-42161: mathmodule.c: move _PyLong_GetOne() loop invariant #26391
  • [3.10] bpo-42161: mathmodule.c: move _PyLong_GetOne() loop invariant (GH-26391) #26393
  • bpo-42161: Hoist the _PyLong_GetOne() call out of the inner loop. #30656
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2021-05-26.22:54:42.058>
    created_at = <Date 2020-10-26.21:59:37.342>
    labels = ['interpreter-core', '3.10']
    title = 'Remove private _PyLong_Zero and _PyLong_One variables'
    updated_at = <Date 2022-01-18.08:02:43.120>
    user = 'https://github.com/vstinner'

    bugs.python.org fields:

    activity = <Date 2022-01-18.08:02:43.120>
    actor = 'rhettinger'
    assignee = 'none'
    closed = True
    closed_date = <Date 2021-05-26.22:54:42.058>
    closer = 'vstinner'
    components = ['Interpreter Core']
    creation = <Date 2020-10-26.21:59:37.342>
    creator = 'vstinner'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 42161
    keywords = ['patch']
    message_count = 16.0
    messages = ['379691', '379700', '379709', '379768', '379798', '379799', '379800', '379804', '379805', '388988', '389002', '389049', '394496', '394498', '394499', '410847']
    nosy_count = 7.0
    nosy_names = ['rhettinger', 'mark.dickinson', 'vstinner', 'serhiy.storchaka', 'corona10', 'pablogsal', 'miss-islington']
    pr_nums = ['22993', '22995', '22998', '23003', '23008', '26391', '26393', '30656']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = None
    url = 'https://bugs.python.org/issue42161'
    versions = ['Python 3.10']

    Activity

    1. vstinner commented on Oct 26, 2020

      @vstinner
      MemberAuthor

      In bpo-38858, I made the small integer singletons per interpreter: commit 630c8df. _PyLong_Zero and _PyLong_One variables are still shared by all interpreters, whereas subinterpreters must not share Python objects: see bpo-40533.

      I propose to add new _PyLong_GetZero() and _PyLong_GetOne() functions to replace _PyLong_Zero and _PyLong_One variables. These functions will retrieve the singletons from tstate->interp->small_ints.

    2. vstinner commented on Oct 26, 2020

      @vstinner
      MemberAuthor

      New changeset 8e3b9f9 by Victor Stinner in branch 'master':
      bpo-42161: Add _PyLong_GetZero() and _PyLong_GetOne() (GH-22993)
      8e3b9f9

    3. vstinner commented on Oct 27, 2020

      @vstinner
      MemberAuthor

      New changeset c9bc290 by Victor Stinner in branch 'master':
      bpo-42161: Use _PyLong_GetZero() and _PyLong_GetOne() (GH-22995)
      c9bc290

    4. vstinner commented on Oct 27, 2020

      @vstinner
      MemberAuthor

      New changeset 3783413 by Victor Stinner in branch 'master':
      bpo-42161: Modules/ uses _PyLong_GetZero() and _PyLong_GetOne() (GH-22998)
      3783413

    5. rhettinger commented on Oct 27, 2020

      @rhettinger
      Contributor

      Why did you put _PyLong_GetOne() inside the loop for the fast path and outside the loop for the slow path?

      ==========================================================================

      diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
      index 8990071f51..0e6c64d1a6 100644
      --- a/Modules/_collectionsmodule.c
      +++ b/Modules/_collectionsmodule.c
      @@ -2278,6 +2278,8 @@ _collections__count_elements_impl(PyObject *module, PyObject *mapping,
           PyObject *dict_get;
           PyObject *mapping_setitem;
           PyObject *dict_setitem;
      +    PyObject *zero = _PyLong_GetZero();  // borrowed reference
      +    PyObject *one = _PyLong_GetOne();    // borrowed reference
      
           it = PyObject_GetIter(iterable);
           if (it == NULL)
      @@ -2324,10 +2326,10 @@ _collections__count_elements_impl(PyObject *module, PyObject *mapping,
                   if (oldval == NULL) {
                       if (PyErr_Occurred())
                           goto done;
      -                if (_PyDict_SetItem_KnownHash(mapping, key, _PyLong_GetOne(), hash) < 0)
      +                if (_PyDict_SetItem_KnownHash(mapping, key, one, hash) < 0)
                           goto done;
                   } else {
      -                newval = PyNumber_Add(oldval, _PyLong_GetOne());
      +                newval = PyNumber_Add(oldval, one);
                       if (newval == NULL)
                           goto done;
                       if (_PyDict_SetItem_KnownHash(mapping, key, newval, hash) < 0)
      @@ -2341,8 +2343,6 @@ _collections__count_elements_impl(PyObject *module, PyObject *mapping,
               if (bound_get == NULL)
                   goto done;
      -        PyObject *zero = _PyLong_GetZero();  // borrowed reference
      -        PyObject *one = _PyLong_GetOne();  // borrowed reference
               while (1) {
                   key = PyIter_Next(it);
                   if (key == NULL)
    6. vstinner commented on Oct 27, 2020

      @vstinner
      MemberAuthor

      Why did you put _PyLong_GetOne() inside the loop for the fast path and outside the loop for the slow path?

      Oh, I didn't notice that the first part was also a loop. I wrote PR 23008 to move the call out of the loop.

      I tried to avoid calling the function if it's possible that the variable it not used. But here, it's always used, so it's relevant to move the loop invariant out of the loop ;-)

    7. vstinner commented on Oct 27, 2020

      @vstinner
      MemberAuthor

      New changeset c310185 by Victor Stinner in branch 'master':
      bpo-42161: Remove private _PyLong_Zero and _PyLong_One (GH-23003)
      c310185

    8. vstinner commented on Oct 27, 2020

      @vstinner
      MemberAuthor

      New changeset 35b95aa by Victor Stinner in branch 'master':
      bpo-42161: Micro-optimize _collections._count_elements() (GH-23008)
      35b95aa

    9. vstinner commented on Oct 27, 2020

      @vstinner
      MemberAuthor

      Thanks Raymond, I fixed the code.

      I close the issue, I removed _PyLong_Zero and _PyLong_One variables.

    10. rhettinger commented on Mar 18, 2021

      @rhettinger
      Contributor

      Thanks Raymond, I fixed the code.

      Can you fix all the other cases where this is used in inner-loop; otherwise, it is undoing everyone else's optimizations and fast paths.

      Also, it seems the that primary motivation for this is support subinterpreters. That PEP hasn't been approved, so we should not be making code worse until we know there is going to some offsetting benefit.

      For example, the inner-loop code in math_lcm() used to have "res == _PyLong_Zero" which was fast a register-to-register comparison (1 cycle at worst and typically 0 cycles with branch prediction). Also there used to be zero memory accesses. The new code has five sequentially dependent operations and four memory accesses (not what we want in an inner-loop):

      movq    __PyRuntime@GOTPCREL(%rip), %rax
      movq    616(%rax), %rax
      movq    16(%rax), %rax
      cmpq    %r12, 3560(%rax)
      jne L326
      

      Ideally, the whole PR should be reverted until the subinterpreter PEP is approved. If not, then at least the changes should be made more carefully, hoisting the new call out of the hot loop:

      +   zero = _PyLong_GetZero();
          for (i = 1; i < nargs; i++) {
              x = PyNumber_Index(args[i]);
              if (x == NULL) {
                  Py_DECREF(res);
                  return NULL;
              }
      -       if (res == _PyLong_GetZero()) {
      +       if (res == zero) {
                  /* Fast path: just check arguments.
                     It is okay to use identity comparison here. */
                  Py_DECREF(x);
                  continue;
              }
              Py_SETREF(res, long_lcm(res, x));
              Py_DECREF(x);
              if (res == NULL) {
                  return NULL;
              }
          }
          return res;
    11. vstinner commented on Mar 18, 2021

      @vstinner
      MemberAuthor

      Since it seems like you have already a ready patch to optimize the code, so go ahead and merge it.

    12. rhettinger commented on Mar 18, 2021

      @rhettinger
      Contributor

      I don't have a PR in-hand. I just ran across this when trying to explain 3.9 vs 3.10 timings and M-1 vs Intel timings. Ideally, all of these PRs should be reverted. Short of that, each change needs to be reviewed to see if it created extra work inside a loop. There are 35 calls to PyLong_GetOne and 3 for PyLong_GetZero. Each of those should be checked.

    13. vstinner commented on May 26, 2021

      @vstinner
      MemberAuthor

      New changeset 3e7ee02 by Victor Stinner in branch 'main':
      bpo-42161: mathmodule.c: move _PyLong_GetOne() loop invariant (GH-26391)
      3e7ee02

    14. vstinner commented on May 26, 2021

      @vstinner
      MemberAuthor

      Raymond: "Can you fix all the other cases where this is used in inner-loop; otherwise, it is undoing everyone else's optimizations and fast paths."

      Done, thanks for the report.

    15. vstinner commented on May 26, 2021

      @vstinner
      MemberAuthor

      New changeset 4115996 by Miss Islington (bot) in branch '3.10':
      bpo-42161: mathmodule.c: move _PyLong_GetOne() loop invariant (GH-26391) (GH-26393)
      4115996

    16. rhettinger commented on Jan 18, 2022

      @rhettinger
      Contributor

      New changeset 243c316 by Raymond Hettinger in branch 'main':
      bpo-42161: Hoist the _PyLong_GetOne() call out of the inner loop. (GH-30656)
      243c316

    17. transferred this issue fromon Apr 10, 2022
    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

      3.10 (EOL)end of lifeinterpreter-core(Objects, Python, Grammar, and Parser dirs)

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions