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-122417: Implement per-thread heap type refcounts #122418

Merged
merged 6 commits into from
Aug 6, 2024

Conversation

colesbury
Copy link
Contributor

@colesbury colesbury commented Jul 29, 2024

The free-threaded build partially stores heap type reference counts in distributed manner in per-thread arrays. This avoids reference count contention when creating or destroying instances of heap types.

The free-threaded build partially stores heap type reference counts in
distributed manner in per-thread arrays. This avoids reference count
contention when creating or destroying instances.

Co-authored-by: Ken Jin <[email protected]>
PyHeapTypeObject *ht = (PyHeapTypeObject *)type;

// Unsigned comparison so that `ht_id=-1` is treated as out-of-bounds.
Py_ssize_t ht_id = ht->_ht_id;
Copy link
Member

@corona10 corona10 Jul 30, 2024

Choose a reason for hiding this comment

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

Nit comment:
Can we extract those two the common static inline function as

  • get_heap_type_id(ht); too nit, please ignore it.
  • check_heap_type_id_assigend(ht_id, size); or heap_type_id_assigend(ht_id, size);

It will be helpful to understand what's happening in here for people like me :)

Copy link
Member

@corona10 corona10 Jul 30, 2024

Choose a reason for hiding this comment

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

By the way, you can ignore this, but some people may need to understand the mechanism when they have to write similar features. At that time it will be helpful to verify ht_id

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 don't see a great way of refactoring out this check -- I think a function name that accurately describes the check will be awkwardly long. It's not just that the heap type has an assigned id (i.e., ht_id != -1), but also that the thread state has space for it its per-thread array (i.e., ht_id < size).

_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)_PyThreadState_GET();
PyHeapTypeObject *ht = (PyHeapTypeObject *)type;

// Unsigned comparison so that `ht_id=-1` is treated as out-of-bounds.
Copy link
Member

@corona10 corona10 Jul 30, 2024

Choose a reason for hiding this comment

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

Or just simply

Suggested change
// Unsigned comparison so that `ht_id=-1` is treated as out-of-bounds.
// Unsigned comparison so that `ht_id=-1` which indicates no thread refcounting is treated as out-of-bounds.

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 updated the comment and moved the "fast path" to the "if" branch instead of the "else"

@kumaraditya303 kumaraditya303 self-requested a review July 31, 2024 04:09
Copy link
Member

@corona10 corona10 left a comment

Choose a reason for hiding this comment

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

lgtm

Thank you for the detail explanation.

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.

A couple of requests and an optional suggestion.

Include/internal/pycore_object.h Show resolved Hide resolved
Include/internal/pycore_object.h Show resolved Hide resolved
@@ -270,6 +270,9 @@ typedef struct _heaptypeobject {
PyObject *ht_module;
char *_ht_tpname; // Storage for "tp_name"; see PyType_FromModuleAndSpec
struct _specialization_cache _spec_cache; // For use by the specializer.
#ifdef Py_GIL_DISABLED
Py_ssize_t _ht_id; // ID used for thread-local refcounting
Copy link
Member

Choose a reason for hiding this comment

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

This seems excessive if you are reusing them. Maybe use a 32 bit number?

Copy link
Member

Choose a reason for hiding this comment

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

Also, _ht_id is not the clearest name. unique_id perhaps (since we know this is a heap type, the ht prefix adds little).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

More than 2^31-1 heap types seems unlikely, but not implausible. It also doesn't save any space, at least not for now, due to padding and alignment.

@bedevere-app
Copy link

bedevere-app bot commented Aug 2, 2024

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

#ifndef Py_GIL_DISABLED
Py_DECREF(type);
#else
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
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
if (!_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
if (_Py_IsImmortal(type)) {

Immortal heap types are allowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Immortal heap types are fine. They either go through the Py_DECREF(), which is a no-op, or do some unnecessary work with the per-thread refcounts, which is also fine.

The assumption in the assert is that static types are immortal. We already assert that elsewhere (in _Py_NewReference).

We could also write:

if (_Py_IsImmortal(type)) {
    return;
}

assert(_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE));

That seems a bit less robust to me in cases where the assumptions are violated, but also fine.

Copy link
Member

Choose a reason for hiding this comment

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

Either works. I think they equivalent in terms of checking assumptions.

@colesbury
Copy link
Contributor Author

@markshannon, I've used macros in the default build and renamed _ht_id to unique_id. I didn't switch to 32-bit ids.

Let me know what you think about the guards in _Py_INCREF_TYPE and _Py_DECREF_TYPE.

I have made the requested changes; please review again.

@bedevere-app
Copy link

bedevere-app bot commented Aug 2, 2024

Thanks for making the requested changes!

@corona10, @markshannon: please review the changes made to this pull request.

@bedevere-app bedevere-app bot requested a review from corona10 August 2, 2024 17:30
@bedevere-app bedevere-app bot requested a review from markshannon August 2, 2024 17:30
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, thanks.

@colesbury colesbury merged commit dc09301 into python:main Aug 6, 2024
38 of 41 checks passed
@colesbury colesbury deleted the gh-122417-type-refcount branch August 6, 2024 18:37
brandtbucher pushed a commit to brandtbucher/cpython that referenced this pull request Aug 7, 2024
)

The free-threaded build partially stores heap type reference counts in
distributed manner in per-thread arrays. This avoids reference count
contention when creating or destroying instances.

Co-authored-by: Ken Jin <[email protected]>
blhsing pushed a commit to blhsing/cpython that referenced this pull request Aug 22, 2024
)

The free-threaded build partially stores heap type reference counts in
distributed manner in per-thread arrays. This avoids reference count
contention when creating or destroying instances.

Co-authored-by: Ken Jin <[email protected]>
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.

4 participants