Commit b5567ae
Backport gh-132657 to 3.14: defer refcount on module values
Summary:
Backport of upstream gh-132657, "If we are specializing to LOAD_GLOBAL_MODULE or
LOAD_ATTR_MODULE, try to enable deferred reference counting for the value, if
the object is owned by a different thread", which landed in CPython 3.15.0a6.
Meta Python 3.14 has neither the helper nor the calls; `third-party/python/main`
already has it via the upstream import.
Stacked on the gh-143469 backport because they touch the same function and only
make sense together: gh-143469 lets the module attribute load specialize at all,
and this one makes the value it caches cheap to load from many threads. Either
alone leaves the common case flat.
`maybe_enable_deferred_ref_count` is copied verbatim from upstream. The two
call sites needed adapting: upstream reads the value out of
`_PyDict_LookupIndexAndValue`, which does not exist in 3.14, so the value comes
from the dict entry instead. That can be NULL for a deleted key at a
still-valid index, which upstream's helper input cannot be, so the call is
NULL-guarded at both sites. Both opcodes already deopt on a NULL entry value at
run time, so behaviour is unchanged.
Scope note: upstream also calls this helper from the class-attribute descriptor
path. That is a different change, and 3.14's version of that code currently
*fails* specialization when the descriptor lacks deferred refcount
(`SPEC_FAIL_ATTR_DESCR_NOT_DEFERRED`) rather than enabling it, so converting it
is not a mechanical backport. Left alone; gh-132657's own NEWS entry names only
`LOAD_GLOBAL_MODULE` and `LOAD_ATTR_MODULE`.
Measured on 3.14.7t against this stack, three modules differing only in the
value they expose and whether they define `__getattr__`, speedup relative to one
thread:
module value 1t ms 8t 32t
mortal, no __getattr__ 10.9 9.0x 19.2x
mortal, has __getattr__ 9.1 7.8x 16.8x
immortal, has __getattr__ 9.2 5.0x 16.6x
untracked, has __getattr__ 11.0 0.5x 0.5x
The second row is the one this diff buys: with only the gh-143469 backport below
it, a mortal module value scored 0.5x. It is now 16.8x, level with an immortal
one.
The last row is the limit, and it is deliberate upstream behaviour rather than a
gap in the backport: the helper is gated on `_PyObject_GC_IS_TRACKED`, and a
plain `object()` is not tracked, so it is skipped. That is exactly the shape of
torch's `dtype` / `layout` / `memory_format` / `qscheme` singletons, which is
why the torch diff above this one still has to make those types GC-tracked
before anything can help them.
CinderX has to move with this. `UpstreamBorrow` copies
`specialize_module_load_attr_lock_held` and `specialize_load_global_lock_held`
out of `Python/specialize.c` and recompiles them, and borrowing is per function
rather than transitive, so the new static helper they now call was not carried
across and the free-threaded cinderx build failed with an implicit-declaration
error. Adding a `Borrow` directive for it to
`borrowed-3.14.free-threading.c.template` fixes that. Only the free-threaded
template needs it; the helper and both call sites are inside `#ifdef
Py_GIL_DISABLED`, so the GIL build compiles them out.
`borrowed-3.14.free-threading.gen_cached.c` is regenerated to match. That file
is a checked-in snapshot and is not consumed by this build config
(`borrowed_library` is called with the default `cached = False`, so the genrule
regenerates from the template), but it is generated output and should not be
left stale. Its delta folds in the body change from the gh-143469 backport
below as well, since that commit alters the same borrowed function and nothing
regenerated the snapshot at that point; no build breaks in between, because
nothing compiles the snapshot.
Validated end to end. Adding `bundle_runtime = True` to a `python_binary`
temporarily builds it against the interpreter from this commit, so the whole
stack can be exercised at once. Probe results at 32 threads, stock 3.14t against
this stack:
case stock stack
touch np.zeros 0.1x 24.7x
touch torch.empty 0.0x 18.3x
touch torch.long 0.1x 16.8x
touch torch.float32 0.1x 16.6x
touch torch.Tensor 0.1x 16.7x
torch.empty(4) 0.1x 11.7x
as_tensor(dtype=long) 0.3x 2.1x
numpy is fixed for free by the two CPython backports, with no numpy change.
Two rows are unmoved and both are expected: `touch math.pi` (0.8x) and
`touch shared obj` (0.5x) are mortal objects that are not GC-tracked, which
`maybe_enable_deferred_ref_count` skips by design.
`as_tensor(dtype=long)` reaching only 2.1x is the honest remaining gap. That
row is an actual tensor *construction*, not a lookup, so what is left is
inside torch: argument parsing, allocation, `TensorImpl` setup. Refcount
contention on the namespace was never going to explain all of it, and this
stack does not address it.
Reviewed By: itamaro
Differential Revision: D116032672
fbshipit-source-id: 2109b9ad7c9122e4635e04fe8f4bc2260ac114471 parent 14b5442 commit b5567ae
3 files changed
Lines changed: 48 additions & 0 deletions
File tree
- cinderx/UpstreamBorrow
Lines changed: 1 addition & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
496 | 496 | | |
497 | 497 | | |
498 | 498 | | |
| 499 | + | |
499 | 500 | | |
500 | 501 | | |
501 | 502 | | |
| |||
Lines changed: 29 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5016 | 5016 | | |
5017 | 5017 | | |
5018 | 5018 | | |
| 5019 | + | |
| 5020 | + | |
| 5021 | + | |
| 5022 | + | |
| 5023 | + | |
| 5024 | + | |
| 5025 | + | |
| 5026 | + | |
| 5027 | + | |
| 5028 | + | |
| 5029 | + | |
5019 | 5030 | | |
5020 | 5031 | | |
5021 | 5032 | | |
| |||
5054 | 5065 | | |
5055 | 5066 | | |
5056 | 5067 | | |
| 5068 | + | |
| 5069 | + | |
| 5070 | + | |
| 5071 | + | |
| 5072 | + | |
| 5073 | + | |
| 5074 | + | |
| 5075 | + | |
| 5076 | + | |
| 5077 | + | |
5057 | 5078 | | |
5058 | 5079 | | |
5059 | 5080 | | |
| |||
5904 | 5925 | | |
5905 | 5926 | | |
5906 | 5927 | | |
| 5928 | + | |
| 5929 | + | |
| 5930 | + | |
| 5931 | + | |
| 5932 | + | |
| 5933 | + | |
| 5934 | + | |
| 5935 | + | |
5907 | 5936 | | |
5908 | 5937 | | |
5909 | 5938 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5170 | 5170 | | |
5171 | 5171 | | |
5172 | 5172 | | |
| 5173 | + | |
| 5174 | + | |
| 5175 | + | |
| 5176 | + | |
| 5177 | + | |
| 5178 | + | |
| 5179 | + | |
| 5180 | + | |
| 5181 | + | |
| 5182 | + | |
5173 | 5183 | | |
5174 | 5184 | | |
5175 | 5185 | | |
| |||
6020 | 6030 | | |
6021 | 6031 | | |
6022 | 6032 | | |
| 6033 | + | |
| 6034 | + | |
| 6035 | + | |
| 6036 | + | |
| 6037 | + | |
| 6038 | + | |
| 6039 | + | |
| 6040 | + | |
6023 | 6041 | | |
6024 | 6042 | | |
6025 | 6043 | | |
| |||
0 commit comments