gh-114733: Cache _PyObject_IS_GC() result in ob_gc_bits (free-threading) - #156994
Closed
NAVEENKUMARKR777 wants to merge 1 commit into
Closed
gh-114733: Cache _PyObject_IS_GC() result in ob_gc_bits (free-threading)#156994NAVEENKUMARKR777 wants to merge 1 commit into
NAVEENKUMARKR777 wants to merge 1 commit into
Conversation
…hreading) In the free-threaded build, _PyObject_IS_GC() is on the hot path for GC tracking and reference-counting decisions, but it always dereferences Py_TYPE(obj) and checks tp_flags (and possibly calls tp_is_gc). Cache the result in the previously-unused high bit of ob_gc_bits at object creation time, so the common case becomes a single relaxed byte load on the object itself. The one type with a non-NULL tp_is_gc (_PyUOpExecutor_Type) can change its "is GC" result after creation (e.g. once made immortal), so objects of that type are deliberately left uncached and always take the original, uncached path. The bit is set once in new_reference(), the common choke point reached by _PyObject_Init() (and therefore _PyObject_New(), _PyObject_GC_New(), PyType_GenericAlloc(), etc.) as well as every freelist-reuse and realloc-based resize path, where Py_TYPE(op) is already valid. This only affects Py_GIL_DISABLED builds; the default build is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Member
|
What would be the performance effect of that change? I don't think new automated PRs are welcome for such delicate part of the interpreter. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements gh-114733, suggested by @nascheme:
_PyObject_IS_GC()is on the hot path for GC tracking and reference-counting decisions in the free-threaded build, but it always dereferencesPy_TYPE(obj)and checkstp_flags(and possibly callstp_is_gc). This caches the result in the previously-unused high bit ofob_gc_bits, set once at object creation time, so the common case becomes a single relaxed byte load on the object itself rather than a separate memory access into the type object.This only affects
Py_GIL_DISABLEDbuilds (the new code is entirely behind#ifdef Py_GIL_DISABLED); the default build is byte-for-byte unchanged.Handling the one dynamic case
Exactly one type in the codebase has a non-NULL
tp_is_gc:_PyUOpExecutor_Type(the tier-2 optimizer's executor object), whoseexecutor_is_gc()result can change after creation (e.g. once the executor is made immortal). Caching a stale bit for this type would be a real correctness hazard, so the bit is only cached whentp_is_gc == NULL— i.e. for every type except this one. Executors always fall through to the original, uncached check, unchanged from today.Where the bit is set
Traced every object-construction path down to the single common choke point,
new_reference()inObjects/object.c:_PyObject_Init()/_PyObject_InitVar()(used by_PyObject_New(),_PyObject_GC_New(),_PyObject_GC_NewVar(),PyType_GenericAlloc()/_PyType_AllocNoTrack(),PyUnstable_Object_GC_NewWithExtraData()) all funnel through here, withPy_TYPE(op)already set.MemoryErrorfreelist) reuse memory whoseob_typeis already correct and cannot have changed, since a type's GC-ness never changes after creation.Testing
_testcapi.pyobject_is_gc()(wrapping the publicPyObject_IS_GC()) and a newIsGCTestinLib/test/test_capi/test_object.pythat checks both freshly allocated and freelist-recycled objects across GC and non-GC types.--disable-gil(free-threaded) variant on Windows.test_capi.test_object(including the new test) passes on both builds.test_gc,test_free_threading, and a 6,884-test sweep acrosstest_builtin,test_types,test_descr,test_dict,test_set,test_list,test_tuple,test_exceptions,test_copy,test_pickle,test_weakref,test_threading,test_asyncio,test_contextvars(test_context),test_generators,test_coroutines,test_class,test_super,test_json,test_re,test_functoolsetc. pass on the free-threaded build.gc.collect()) with no failures.Test plan
IsGCTest) passes on both GIL-enabled and free-threaded builds.🤖 Generated with Claude Code