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

Use per-thread reference count for heap type objects in the free-threaded build #122417

Closed
colesbury opened this issue Jul 29, 2024 · 2 comments
Closed
Labels
topic-free-threading type-feature A feature request or enhancement

Comments

@colesbury
Copy link
Contributor

colesbury commented Jul 29, 2024

Feature or enhancement

Overview

As described in PEP 703, heap type objects use a mix of reference counting techniques in the free-threaded build. They use deferred reference counting (see #117376) in combination with with per-thread reference counting. Deferred reference counting alone is not sufficient to address the multi-threaded scaling bottlenecks with heap types because most references to heap types are from object instances, not references on the interpreter stack.

To address this, heap type reference counts are partially stored in a distributed manner in per-thread arrays. Every thread stores an array of local reference counts for each heap type object. A heap type's true reference count is the sum of:

  • The PyObject reference count (i.e., ob_ref_local and ob_ref_shared)
  • The deferred references on the stack (like with other objects that use deferred reference counting)
  • The per-thread reference counts stored in each _PyThreadStateImpl

Mechanics

Two new internal functions for reference counting types:

  • void _Py_INCREF_TYPE(PyTypeObject *type)
  • void _Py_DECREF_TYPE(PyTypeObject *type)

These are like Py_INCREF and Py_DECREF, but modify the per-thread reference counts when possible. If necessary, they fallback to Py_INCREF/DECREF. In the default build, these just call Py_INCREF/DECREF. Note that it's always safe to call Py_INCREF instead of _Py_INCREF_TYPE; it's just that _Py_INCREF_TYPE avoids scalability bottlenecks in some cases.

The free-threaded GC will scan the per-thread refcount arrays when computing an object's reference count in addition to scanning each thread's stack for deferred reference counting.

We'll also need some code to manage the lifetime and resizing of the per-thread refcount arrays.

Internal Usages

_PyObject_Init should call _Py_INCREF_TYPE(typeobj) instead of Py_INCREF(typeobj). (The PEP says PyType_GenericAlloc, but _PyObject_Init is now the right place to do it.)

subtype_dealloc should call _Py_DECREF_TYPE(type) instead of Py_DECREF(type). (Same as described in the PEP).

Additionally, with the conversion of some static types to heap types, we now use the following tp_dealloc pattern in more places:

PyTypeObject *tp = Py_TYPE(self);
tp->tp_free(self);
Py_DECREF(tp);

We will eventually want to change internal usages to the following to avoid reference count contention on tp:

PyTypeObject *tp = Py_TYPE(self);
tp->tp_free(self);
_Py_DECREF_TYPE(tp);

Linked PRs

@colesbury colesbury added type-feature A feature request or enhancement topic-free-threading labels Jul 29, 2024
colesbury added a commit to colesbury/cpython that referenced this issue 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.

Co-authored-by: Ken Jin <kenjin@python.org>
colesbury added a commit to colesbury/cpython that referenced this issue 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.

Co-authored-by: Ken Jin <kenjin@python.org>
colesbury added a commit to colesbury/cpython that referenced this issue 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.

Co-authored-by: Ken Jin <kenjin@python.org>
@markshannon
Copy link
Member

Since this delays freeing heap types until the next collection, would it make sense to defer the references from instances to their class?

It we make all references from objects headers to their class deferred then there will be no need to update the reference count at all. It would be simpler than this mechanism.
Given you need to scan all the objects anyway during GC, it would also be lower overhead.

@colesbury
Copy link
Contributor Author

Since this delays freeing heap types until the next collection, would it make sense to defer the references from instances to their class?

I don't think we can treat the instance -> type references like we treat stack references. The stacks are always visible to the GC, but not every instance is.

Given you need to scan all the objects anyway during GC...

We only scan GC tracked objects (and stacks). We don't visit untracked objects or non-GC objects, but those objects should still keep their types alive.

colesbury added a commit that referenced this issue Aug 6, 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 <kenjin@python.org>
brandtbucher pushed a commit to brandtbucher/cpython that referenced this issue 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 <kenjin@python.org>
blhsing pushed a commit to blhsing/cpython that referenced this issue 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 <kenjin@python.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-free-threading type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

2 participants