@@ -255,6 +255,9 @@ managed_static_type_state_init(PyInterpreterState *interp, PyTypeObject *self,
255
255
assert (state -> type == NULL );
256
256
state -> type = self ;
257
257
state -> isbuiltin = isbuiltin ;
258
+ #ifdef Py_GIL_DISABLED
259
+ state -> local_cache .tp_version_tag = self -> tp_version_tag ;
260
+ #endif
258
261
259
262
/* state->tp_subclasses is left NULL until init_subclasses() sets it. */
260
263
/* state->tp_weaklist is left NULL until insert_head() or insert_after()
@@ -290,6 +293,12 @@ managed_static_type_state_clear(PyInterpreterState *interp, PyTypeObject *self,
290
293
assert (state -> type != NULL );
291
294
state -> type = NULL ;
292
295
assert (state -> tp_weaklist == NULL ); // It was already cleared out.
296
+ #ifdef Py_GIL_DISABLED
297
+ for (Py_ssize_t i = 0 ; i < LOCAL_TYPE_CACHE_SIZE ; i ++ ) {
298
+ Py_CLEAR (state -> local_cache .entries [i ].name );
299
+ state -> local_cache .entries [i ].value = NULL ;
300
+ }
301
+ #endif
293
302
294
303
(void )_Py_atomic_add_int64 (
295
304
& _PyRuntime .types .managed_static .types [full_index ].interp_count , -1 );
@@ -1021,6 +1030,33 @@ set_version_unlocked(PyTypeObject *tp, unsigned int version)
1021
1030
#endif
1022
1031
}
1023
1032
1033
+ static void
1034
+ clear_spec_cache (PyTypeObject * type )
1035
+ {
1036
+ if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
1037
+ // This field *must* be invalidated if the type is modified (see the
1038
+ // comment on struct _specialization_cache):
1039
+ PyHeapTypeObject * heap_type = (PyHeapTypeObject * )type ;
1040
+ FT_ATOMIC_STORE_PTR_RELAXED (
1041
+ heap_type -> _spec_cache .getitem , NULL );
1042
+ #ifdef Py_GIL_DISABLED
1043
+ struct local_type_cache * cache = heap_type -> _spec_cache .local_type_cache ;
1044
+ if (cache != NULL ) {
1045
+ FT_ATOMIC_STORE_PTR_RELAXED (
1046
+ heap_type -> _spec_cache .local_type_cache , NULL );
1047
+ for (Py_ssize_t i = 0 ; i < LOCAL_TYPE_CACHE_SIZE ; i ++ ) {
1048
+ PyObject * name = _Py_atomic_load_ptr_relaxed (& cache -> entries [i ].name );
1049
+ if (name != NULL ) {
1050
+ _Py_atomic_store_ptr_release (& cache -> entries [i ].name , NULL );
1051
+ Py_DECREF (name );
1052
+ }
1053
+ }
1054
+ _PyMem_FreeDelayed (cache );
1055
+ }
1056
+ #endif
1057
+ }
1058
+ }
1059
+
1024
1060
static void
1025
1061
type_modified_unlocked (PyTypeObject * type )
1026
1062
{
@@ -1083,12 +1119,7 @@ type_modified_unlocked(PyTypeObject *type)
1083
1119
}
1084
1120
1085
1121
set_version_unlocked (type , 0 ); /* 0 is not a valid version tag */
1086
- if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
1087
- // This field *must* be invalidated if the type is modified (see the
1088
- // comment on struct _specialization_cache):
1089
- FT_ATOMIC_STORE_PTR_RELAXED (
1090
- ((PyHeapTypeObject * )type )-> _spec_cache .getitem , NULL );
1091
- }
1122
+ clear_spec_cache (type );
1092
1123
}
1093
1124
1094
1125
void
@@ -1165,12 +1196,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
1165
1196
assert (!(type -> tp_flags & _Py_TPFLAGS_STATIC_BUILTIN ));
1166
1197
set_version_unlocked (type , 0 ); /* 0 is not a valid version tag */
1167
1198
type -> tp_versions_used = _Py_ATTR_CACHE_UNUSED ;
1168
- if (PyType_HasFeature (type , Py_TPFLAGS_HEAPTYPE )) {
1169
- // This field *must* be invalidated if the type is modified (see the
1170
- // comment on struct _specialization_cache):
1171
- FT_ATOMIC_STORE_PTR_RELAXED (
1172
- ((PyHeapTypeObject * )type )-> _spec_cache .getitem , NULL );
1173
- }
1199
+ clear_spec_cache (type );
1174
1200
}
1175
1201
1176
1202
/*
@@ -5542,6 +5568,119 @@ _PyTypes_AfterFork(void)
5542
5568
#endif
5543
5569
}
5544
5570
5571
+ #ifdef Py_GIL_DISABLED
5572
+
5573
+ static bool
5574
+ can_cache_locally (PyTypeObject * type , PyObject * name ) {
5575
+ // We don't cache types in l1 for anything which is a custom get attr, it's likely
5576
+ // to have many dynamic attributes (think modules and metaclasses).
5577
+ // We also only cache interned or immortal strings.
5578
+ return type -> tp_getattro == PyObject_GenericGetAttr &&
5579
+ (PyUnicode_CHECK_INTERNED (name ) != SSTATE_NOT_INTERNED || _Py_IsImmortal (name ));
5580
+ }
5581
+
5582
+ static struct local_type_cache *
5583
+ get_local_type_cache (PyTypeObject * type , unsigned int assigned_version )
5584
+ {
5585
+ unsigned long flags = FT_ATOMIC_LOAD_ULONG_RELAXED (type -> tp_flags );
5586
+
5587
+ if (flags & Py_TPFLAGS_HEAPTYPE ) {
5588
+ PyHeapTypeObject * heap_type = (PyHeapTypeObject * )type ;
5589
+ struct local_type_cache * local_cache = _Py_atomic_load_ptr_acquire (& heap_type -> _spec_cache .local_type_cache );
5590
+ if (local_cache == NULL && assigned_version ) {
5591
+ local_cache = PyMem_Calloc (1 , sizeof (struct local_type_cache ));
5592
+ local_cache -> tp_version_tag = assigned_version ;
5593
+ _Py_atomic_store_ptr_release (& heap_type -> _spec_cache .local_type_cache , local_cache );
5594
+ }
5595
+ return local_cache ;
5596
+ } else if (flags & _Py_TPFLAGS_STATIC_BUILTIN ) {
5597
+ PyInterpreterState * interp = _PyInterpreterState_GET ();
5598
+ managed_static_type_state * state = managed_static_type_state_get (interp , type );
5599
+ return & state -> local_cache ;
5600
+ }
5601
+ return NULL ;
5602
+ }
5603
+
5604
+ #define HASH_NAME (name ) (((Py_ssize_t)(name)) >> LOCAL_TYPE_CACHE_PROBE)
5605
+
5606
+ static bool
5607
+ try_local_cache_lookup (PyTypeObject * type , PyObject * name , PyObject * * value , unsigned int * version )
5608
+ {
5609
+ if (!can_cache_locally (type , name )) {
5610
+ return false;
5611
+ }
5612
+
5613
+ struct local_type_cache * local_cache = get_local_type_cache (type , 0 );
5614
+ if (local_cache == NULL ) {
5615
+ return false;
5616
+ }
5617
+
5618
+ Py_ssize_t index = HASH_NAME (name ) % LOCAL_TYPE_CACHE_SIZE ;
5619
+ Py_ssize_t cur = index ;
5620
+ do {
5621
+ struct local_type_cache_entry * entry = & local_cache -> entries [cur ];
5622
+ PyObject * entry_name = _Py_atomic_load_ptr_acquire (& entry -> name );
5623
+ if (entry_name == name ) {
5624
+ // Value is set as maybe weakref'd, and the per-type cache never replaces
5625
+ // values so we get away w/ a simple incref here.
5626
+ PyObject * entry_value = _Py_atomic_load_ptr_relaxed (& entry -> value );
5627
+ Py_XINCREF (entry_value );
5628
+ * value = entry_value ;
5629
+
5630
+ if (version ) {
5631
+ * version = local_cache -> tp_version_tag ;
5632
+ }
5633
+
5634
+ return true;
5635
+ }
5636
+ else if (entry_name == NULL ) {
5637
+ break ;
5638
+ }
5639
+ cur = (cur + LOCAL_TYPE_CACHE_PROBE ) % LOCAL_TYPE_CACHE_SIZE ;
5640
+ } while (cur != index );
5641
+ return false;
5642
+ }
5643
+
5644
+ static bool
5645
+ cache_local_type_lookup (PyTypeObject * type , PyObject * name ,
5646
+ PyObject * res , unsigned int assigned_version )
5647
+ {
5648
+ if (!can_cache_locally (type , name ) ||
5649
+ type -> tp_versions_used >= MAX_VERSIONS_PER_CLASS ) {
5650
+ return false;
5651
+ }
5652
+
5653
+ struct local_type_cache * local_cache = get_local_type_cache (type , assigned_version );
5654
+ if (local_cache == NULL ||
5655
+ local_cache -> cache_count >= LOCAL_TYPE_CACHE_MAX_ENTRIES ) {
5656
+ return false;
5657
+ }
5658
+
5659
+ Py_ssize_t index = HASH_NAME (name ) % LOCAL_TYPE_CACHE_SIZE ;
5660
+ Py_ssize_t cur = index ;
5661
+ do {
5662
+ struct local_type_cache_entry * entry = & local_cache -> entries [cur ];
5663
+ PyObject * entry_name = _Py_atomic_load_ptr_relaxed (& entry -> name );
5664
+ if (entry_name == NULL ) {
5665
+ if (res != NULL ) {
5666
+ // Reads from other threads can proceed lock-free.
5667
+ _PyObject_SetMaybeWeakref (res );
5668
+ }
5669
+
5670
+ // Value is written first, then name, so when name is read the
5671
+ // value is always present.
5672
+ _Py_atomic_store_ptr_relaxed (& entry -> value , res );
5673
+ _Py_atomic_store_ptr_release (& entry -> name , Py_NewRef (name ));
5674
+ local_cache -> cache_count ++ ;
5675
+ return true;
5676
+ }
5677
+ cur = (cur + LOCAL_TYPE_CACHE_PROBE ) % LOCAL_TYPE_CACHE_SIZE ;
5678
+ } while (cur != index );
5679
+ return false;
5680
+ }
5681
+
5682
+ #endif
5683
+
5545
5684
/* Internal API to look for a name through the MRO.
5546
5685
This returns a strong reference, and doesn't set an exception!
5547
5686
If nonzero, version is set to the value of type->tp_version at the time of
@@ -5551,13 +5690,22 @@ PyObject *
5551
5690
_PyType_LookupRefAndVersion (PyTypeObject * type , PyObject * name , unsigned int * version )
5552
5691
{
5553
5692
PyObject * res ;
5693
+
5694
+ #ifdef Py_GIL_DISABLED
5695
+ // Free-threaded, try a completely lock-free per-type L1 cache first
5696
+ if (try_local_cache_lookup (type , name , & res , version )) {
5697
+ return res ;
5698
+ }
5699
+ #endif
5700
+
5554
5701
int error ;
5555
5702
PyInterpreterState * interp = _PyInterpreterState_GET ();
5556
-
5557
5703
unsigned int h = MCACHE_HASH_METHOD (type , name );
5558
5704
struct type_cache * cache = get_type_cache ();
5559
5705
struct type_cache_entry * entry = & cache -> hashtable [h ];
5706
+
5560
5707
#ifdef Py_GIL_DISABLED
5708
+ // Fall back to global L2 cache which requires sequence locks
5561
5709
// synchronize-with other writing threads by doing an acquire load on the sequence
5562
5710
while (1 ) {
5563
5711
uint32_t sequence = _PySeqLock_BeginRead (& entry -> sequence );
@@ -5574,6 +5722,7 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
5574
5722
if (version != NULL ) {
5575
5723
* version = entry_version ;
5576
5724
}
5725
+
5577
5726
return value ;
5578
5727
}
5579
5728
Py_XDECREF (value );
@@ -5612,12 +5761,20 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
5612
5761
5613
5762
int has_version = 0 ;
5614
5763
unsigned int assigned_version = 0 ;
5764
+
5765
+ bool locally_cached = false;
5615
5766
BEGIN_TYPE_LOCK ();
5767
+
5616
5768
res = find_name_in_mro (type , name , & error );
5617
5769
if (MCACHE_CACHEABLE_NAME (name )) {
5618
5770
has_version = assign_version_tag (interp , type );
5619
5771
assigned_version = type -> tp_version_tag ;
5620
5772
}
5773
+
5774
+ #ifdef Py_GIL_DISABLED
5775
+ locally_cached = has_version && !error &&
5776
+ cache_local_type_lookup (type , name , res , assigned_version );
5777
+ #endif
5621
5778
END_TYPE_LOCK ();
5622
5779
5623
5780
/* Only put NULL results into cache if there was no error. */
@@ -5640,9 +5797,10 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
5640
5797
return NULL ;
5641
5798
}
5642
5799
5643
- if (has_version ) {
5800
+ if (has_version && ! locally_cached ) {
5644
5801
#if Py_GIL_DISABLED
5645
5802
update_cache_gil_disabled (entry , name , assigned_version , res );
5803
+
5646
5804
#else
5647
5805
PyObject * old_value = update_cache (entry , name , assigned_version , res );
5648
5806
Py_DECREF (old_value );
@@ -6164,6 +6322,7 @@ type_dealloc(PyObject *self)
6164
6322
}
6165
6323
Py_XDECREF (et -> ht_module );
6166
6324
PyMem_Free (et -> _ht_tpname );
6325
+ clear_spec_cache (type );
6167
6326
#ifdef Py_GIL_DISABLED
6168
6327
assert (et -> unique_id == _Py_INVALID_UNIQUE_ID );
6169
6328
#endif
0 commit comments