Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-131586: Avoid refcount contention in some "special" calls #131588

Merged
merged 6 commits into from
Mar 26, 2025

Conversation

colesbury
Copy link
Contributor

@colesbury colesbury commented Mar 22, 2025

In the free threaded build, the _PyObject_LookupSpecial() call can lead to reference count contention on the returned function object becuase it doesn't use stackrefs. Refactor some of the callers to use _PyObject_MaybeCallSpecialNoArgs, which uses stackrefs internally.

This fixes the scaling bottleneck in the "lookup_special" microbenchmark in ftscalingbench.py. However, the are still some uses of _PyObject_LookupSpecial() that need to be addressed in future PRs.

In the free threaded build, the `_PyObject_LookupSpecial()` call can lead to
reference count contention on the returned function object becuase it
doesn't use stackrefs. Refactor some of the callers to use
`_PyObject_MaybeCallSpecialNoArgs`, which uses stackrefs internally.

This fixes the scaling bottleneck in the "lookup_special" microbenchmark
in `ftscalingbench.py`. However, the are still some uses of
`_PyObject_LookupSpecial()` that need to be addressed in future PRs.
if (method != NULL) {
PyObject *result = _PyObject_CallNoArgs(method);
Py_DECREF(method);
PyObject *result = _PyObject_MaybeCallSpecialNoArgs(number, &_Py_ID(__ceil__));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice approach, we don't need module state anymore for this case.
cc @vstinner

@colesbury colesbury marked this pull request as ready for review March 24, 2025 13:31
@colesbury colesbury requested a review from mpage March 24, 2025 13:32
@colesbury colesbury added the 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section label Mar 24, 2025
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @colesbury for commit 93c8143 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F131588%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section label Mar 24, 2025
Copy link
Contributor

@mpage mpage left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 5732 to 5736
/* Internal API to look for a name through the MRO.
This returns a strong reference, and doesn't set an exception!
If nonzero, version is set to the value of type->tp_version at the time of
the lookup.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs to be updated.

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall design LGTM and the implementation looks correct. But I don't understand well StackRef, so I prefer to abstain from approving this PR.

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. One quibble about the naming of a return type.

OOI, do you have any numbers of the impact of the PR, either stats or benchmarks?

@@ -890,6 +890,8 @@ extern bool _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name,
PyObject **attr);
extern PyObject *_PyType_LookupRefAndVersion(PyTypeObject *, PyObject *,
unsigned int *);
extern unsigned int
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extern unsigned int
extern uint32_t

int returns imply a very limited range of values, usually only 0/-1. Please use C99 int types when returning actual data, rather just an error code.

Should the comment explaining what it does go here, rather than on the implementation?
It seems to me that "what" comments go in headers, and "how" comments in the C code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the comment to the header file. The return value is unsigned int because tp_version_tag (in PyTypeObject) is an unsigned int and all the other _PyType_Lookup variants store the version tag as an unsigned int.

@colesbury

This comment was marked as outdated.

@colesbury

This comment was marked as outdated.

@colesbury
Copy link
Contributor Author

Oops... those are the numbers for a different PR (#130064). Performance looks unchanged with this PR (<0.1%)

@colesbury colesbury merged commit 67fbfb4 into python:main Mar 26, 2025
41 checks passed
@colesbury colesbury deleted the gh-131586-lookup-special branch March 26, 2025 18:38
@vstinner
Copy link
Member

Nice enhancement.

diegorusso pushed a commit to diegorusso/cpython that referenced this pull request Apr 1, 2025
…ython#131588)

In the free threaded build, the `_PyObject_LookupSpecial()` call can lead to
reference count contention on the returned function object becuase it
doesn't use stackrefs. Refactor some of the callers to use
`_PyObject_MaybeCallSpecialNoArgs`, which uses stackrefs internally.

This fixes the scaling bottleneck in the "lookup_special" microbenchmark
in `ftscalingbench.py`. However, the are still some uses of
`_PyObject_LookupSpecial()` that need to be addressed in future PRs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants