-
Notifications
You must be signed in to change notification settings - Fork 1
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
Add unstable C API function for enabling deferred reference counting (PyUnstable_Object_EnableDeferredRefcount
)
#42
Comments
My motivation is to make my cache package scalable, but it's still unclear to me how
|
FWIW, your case is somewhat specific. A bound method object ( |
I suggest I think it should be easier to add/remove reasons why deferred refcounting can't be enabled. Suggestion:
Keep in mind |
I think that it's reasonable addition, but I'm not sure about the API. I'm waiting to see a concrete API :-) I don't understand well when such call can fail. |
@vstinner: the linked PR (python/cpython#123635) has a concrete API. The API only fails if the pre-conditions are not met:
|
Because there's only one zero, but a lot of positive values :) You could also do it with a pointer-to-result argument:
IMO, that's a detail handled by whoever defines the type. For whoever uses the object, it should be OK to ignore the detail -- the indication that nothing happened should be enough. |
Why not setting an exception with an error message, rather than an error code? |
Bikeshedding: I like
I think we're almost certainly going to get users that don't understand how DRC works, and only know that it helps speed up threads, so they'll just apply it to every object they see. If trying to enable it on a very clearly incorrect object doesn't raise an exception, we're bound to have users putting no-ops everywhere -- wouldn't it be better to prevent that? |
Because
I wouldn't worry about users doing misguided |
At this point, do we even need three different return codes? If we go with |
I see 3 cases:
|
This could be a |
PyUnstable_Object_SetDeferredRefcount
)PyUnstable_Object_EnableDeferredRefcount
)
The I expect extensions to use this on instances of their own types during object construction. That's how we use it internally too and I think we should be explicit about that in the documentation. For what it's worth, I do not expect this to be widely used, but it is important for the few extensions that will make use of it. I see the three cases as:
I think the I don't think we should expose |
It seems like we have an agreement on the API, so let's vote. Vote: API:
|
+0 for this as unstable API, with the mentioned docs note:
(Without this, an extension that would change one of its types from GC-tracked to untracked would be making a backwards incompatible change.) |
Does this mean it is permitted for an implementation to always return 0, or is it obliged to check if the object was already using deferred reference counting and return 1 if it wasn't? (We plan to use deferred reference counting for the default build) |
Is an implementation allowed to return 1 if an object was already using deferred reference counting? The specification is vague. |
I think this would be better:
I don't see a need for this function to return -1 and raise an exception. If calling |
@erlend-aasland @mdboom @serhiy-storchaka: You didn't vote on this API yet. What's your call on it? |
The current implementation fails if PyType_IS_GC() is false: if the object is not traked by the GC. In general, it's better to have an error path. |
So?
Why? This supposed point of this function is to hint that reclamation of an object can be deferred. There is no reason for setting an exception, it just makes the function harder to use. |
I removed my vote since there is ongoing discussion. |
@colesbury @ZeroIntensity: What's your opinion on the "error case"? Should the function return -1 and set an exception, or return 0 and "ignore the error"? |
As a user, exception messages are helpful when debugging--I'm all too familiar with digging through source to find why something is failing. No-ops, to me, are only useful if there's absolutely nothing I can do about it, such as on a GIL-enabled build (rather than the other case of trying to use it on an untracked type, in which case I should just remove the call entirely).
|
I had a slight preference for |
Why is calling
|
It's a hint to use deferred reference counting and we don't support that on non-GC types. |
There are a couple of problems with that:
What a user should know, and doesn't depend on the implementation, is whether an object is likely to be long-lived and whether it is OK if its reclamation is deferred. Or, if we don't want that yet, keep |
This still applies to the no-op case. Arguably, that makes it more difficult for users to figure out why the function isn't working. Is it possible for this function to work on non-GC types in the future? |
Just because the VM doesn't do anything with that hint doesn't mean the function has failed. It always works. |
In the interest of moving this along, let's remove the
No - we have multiple mechanisms that defer reclamation and this is one specific mechanism.
This is addressed in the "Motivations" and "Alternatives". I don't think that's a good idea. |
Bikeshedding: |
Would it be better to have an API that says you are OK with deferred reclamation and let the VM decide which mechanism to use? |
Some of the deferred reclamation mechanisms are primarily about thread-safety 1 as opposed to avoiding reference count contention. I don't think the VM has enough information to decide. I would rather have this API concretely do one thing. Footnotes
|
I've given in and updated the PR to no-op on non-GC types. Are there any other concerns about the API? |
With Mark being strongly for the no-fail API, and Sam not opposed to it, I think it's good to go. (FWIW, I think the discussion was a bit too thorough, and unstable API shouldn't need C API WG approval at all. But, you do get better API by asking -- or at least better-considered API.) |
In the free-threaded build, marking an object as supporting deferred reference counting allows the interpreter to skip reference count modifications for values pushed and popped from the interpreter's evaluation stack and local variables. This is important for avoiding contention on reference count modifications that inhibit scaling. See also PEP 703: "Deferred Reference Counting". The tradeoff is that these objects are only collected when the tracing GC runs.
In the free-threaded build, we currently have an internal-only function
_PyObject_SetDeferredRefcount
that allows an object to have deferred references to it. We use this on methods, top-level functions, modules, heap types, and a few other types of objects.I think we should expose this as an unstable API for C extensions that "hints" the runtime to make this tradeoff:
By "hint", I mean that the runtime is free to ignore this suggestion. In particular, the the function is a no-op in the default build.
Motivation
Some C API extensions like nanobind create their own function and method-like objects. The
nanobind.nb_func
andnanobind.nb_method
behave like functions and methods, but they are not subclasses ofPyFunction_Type
,PyCFunction_Type
, orPyMethod_Type
.Without deferred reference counting (or immortalization) enabled, calling nanobind functions from multiple threads doesn't scale at all -- nanobind would not be very useful in the free-threaded build.
I think this is likely to come up in other extensions as well -- Peter's PR was created in response to a separate issue by @Yiling-J, for example.
Alternatives
Nanobind's support for free-threading currently relies on making
nb_func
andnb_method
objects immortal in the free-threaded build immortal by basically copy-pasting_Py_SetImmortal
. That's obviously not great - I'd much rather that nanobind is able to use an official, unstable API than to modify internals in a way that's likely to break in new CPython versions.Note that in 3.13 deferred reference counting and immortalization are entangled due to time limitations, but this is no longer the case in main/3.14.
Python API?
I don't think we should expose this as a Python API, at least for now.
cc @wjakob, @ngoldbaum, @ZeroIntensity
The text was updated successfully, but these errors were encountered: