You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Remove private _PyLong_Zero and _PyLong_One variables #86327
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.
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 ;-)
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):
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;
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.
Activity
vstinner commentedon Oct 26, 2020
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.
vstinner commentedon Oct 26, 2020
New changeset 8e3b9f9 by Victor Stinner in branch 'master':
bpo-42161: Add _PyLong_GetZero() and _PyLong_GetOne() (GH-22993)
8e3b9f9
vstinner commentedon Oct 27, 2020
New changeset c9bc290 by Victor Stinner in branch 'master':
bpo-42161: Use _PyLong_GetZero() and _PyLong_GetOne() (GH-22995)
c9bc290
vstinner commentedon Oct 27, 2020
New changeset 3783413 by Victor Stinner in branch 'master':
bpo-42161: Modules/ uses _PyLong_GetZero() and _PyLong_GetOne() (GH-22998)
3783413
rhettinger commentedon Oct 27, 2020
Why did you put _PyLong_GetOne() inside the loop for the fast path and outside the loop for the slow path?
==========================================================================
vstinner commentedon Oct 27, 2020
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 ;-)
vstinner commentedon Oct 27, 2020
New changeset c310185 by Victor Stinner in branch 'master':
bpo-42161: Remove private _PyLong_Zero and _PyLong_One (GH-23003)
c310185
vstinner commentedon Oct 27, 2020
New changeset 35b95aa by Victor Stinner in branch 'master':
bpo-42161: Micro-optimize _collections._count_elements() (GH-23008)
35b95aa
vstinner commentedon Oct 27, 2020
Thanks Raymond, I fixed the code.
I close the issue, I removed _PyLong_Zero and _PyLong_One variables.
rhettinger commentedon Mar 18, 2021
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):
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:
vstinner commentedon Mar 18, 2021
Since it seems like you have already a ready patch to optimize the code, so go ahead and merge it.
rhettinger commentedon Mar 18, 2021
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.
vstinner commentedon May 26, 2021
New changeset 3e7ee02 by Victor Stinner in branch 'main':
bpo-42161: mathmodule.c: move _PyLong_GetOne() loop invariant (GH-26391)
3e7ee02
vstinner commentedon May 26, 2021
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.
vstinner commentedon May 26, 2021
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
rhettinger commentedon Jan 18, 2022
New changeset 243c316 by Raymond Hettinger in branch 'main':
bpo-42161: Hoist the _PyLong_GetOne() call out of the inner loop. (GH-30656)
243c316