@@ -41,31 +41,36 @@ static inline size_t cache_nbytes(struct type_cache *cache)
4141 + (size_t )cache_size (cache ) * sizeof (struct type_cache_entry );
4242}
4343
44- static struct type_cache * allocate_cache (uint32_t size )
44+ static struct type_cache * cache_allocate (uint32_t size )
4545{
46+ // size must be a power of two
4647 assert ((size & (size - 1 )) == 0 );
4748 struct type_cache * cache = PyMem_Calloc (1 , sizeof (struct type_cache ) + size * sizeof (struct type_cache_entry ));
4849 if (cache == NULL ) {
4950 return NULL ;
5051 }
5152 cache -> mask = size - 1 ;
53+ // load factor of 0.75
5254 cache -> available = size - (size >> 2 );
5355 cache -> used = 0 ;
5456 return cache ;
5557}
5658
57- static void free_cache_delayed (struct type_cache * cache )
59+ static void cache_free_delayed (struct type_cache * cache )
5860{
5961 if (cache == NULL || cache == & empty_cache ) {
6062 return ;
6163 }
6264#ifndef Py_GIL_DISABLED
65+ // On gil-enabled builds, the cache owns strong references to the interned strings,
66+ // so we need to decref them before freeing the cache memory.
6367 for (uint32_t i = 0 ; i < cache_size (cache ); i ++ ) {
6468 if (cache -> hashtable [i ].name != NULL ) {
6569 Py_DECREF (cache -> hashtable [i ].name );
6670 }
6771 }
6872#endif
73+ // Delay the freeing of old cache for concurrent lock-free readers
6974 _PyMem_FreeDelayed (cache , cache_nbytes (cache ));
7075}
7176
@@ -81,12 +86,12 @@ static inline void **cache_slot(PyTypeObject *type)
8186 return & type -> _tp_cache ;
8287}
8388
84- static inline struct type_cache * get_cache (PyTypeObject * type )
89+ static inline struct type_cache * cache_get (PyTypeObject * type )
8590{
8691 return (struct type_cache * )FT_ATOMIC_LOAD_PTR (* cache_slot (type ));
8792}
8893
89- static inline void set_cache (PyTypeObject * type , struct type_cache * cache )
94+ static inline void cache_set (PyTypeObject * type , struct type_cache * cache )
9095{
9196 FT_ATOMIC_STORE_PTR (* cache_slot (type ), cache );
9297}
@@ -96,7 +101,7 @@ void _PyTypeCache_InitType(PyTypeObject *type)
96101 * cache_slot (type ) = & empty_cache ;
97102}
98103
99- static inline void type_cache_insert (struct type_cache * cache , PyObject * name ,
104+ static inline void cache_insert (struct type_cache * cache , PyObject * name ,
100105 PyObject * value )
101106{
102107 Py_hash_t hash = PyUnstable_Unicode_GET_CACHED_HASH (name );
@@ -115,57 +120,63 @@ static inline void type_cache_insert(struct type_cache *cache, PyObject *name,
115120 return ;
116121 }
117122 else if (cache -> hashtable [index ].name == name ) {
123+ /* someone else added the entry before us. */
118124 return ;
119125 }
120126 index = (index + 1 ) & cache -> mask ;
121127 }
122128}
123129
124- static inline int type_cache_resize (PyTypeObject * type , struct type_cache * cache )
130+ static inline int cache_resize (PyTypeObject * type , struct type_cache * cache )
125131{
126132 uint32_t old_size = cache_size (cache );
127133 uint32_t new_size ;
128134 if (cache -> used == 0 ) {
135+ // the cache is the empty cache, we need to allocate a new cache with the minimum size
129136 new_size = _Py_TYPECACHE_MINSIZE ;
130137 }
131138 else {
139+ // double the cache size when resizing
132140 new_size = old_size * 2 ;
133141 }
134- struct type_cache * new_cache = allocate_cache (new_size );
142+ struct type_cache * new_cache = cache_allocate (new_size );
135143 if (new_cache == NULL ) {
136144 return -1 ;
137145 }
138146 FT_ATOMIC_STORE_UINT_RELAXED (cache -> version_tag , FT_ATOMIC_LOAD_UINT_RELAXED (type -> tp_version_tag ));
139147 for (uint32_t i = 0 ; i < old_size ; i ++ ) {
140148 if (cache -> hashtable [i ].name != NULL ) {
141- type_cache_insert (new_cache , cache -> hashtable [i ].name ,
149+ cache_insert (new_cache , cache -> hashtable [i ].name ,
142150 cache -> hashtable [i ].value );
143151 }
144152 }
145- set_cache (type , new_cache );
146- free_cache_delayed (cache );
153+ cache_set (type , new_cache );
154+ cache_free_delayed (cache );
147155 return 0 ;
148156}
149157
150158void _PyTypeCache_Insert (PyTypeObject * type , PyObject * name , PyObject * value )
151159{
152- struct type_cache * cache = get_cache (type );
160+ struct type_cache * cache = cache_get (type );
161+ // If the cache is full, resize it before inserting the new entry.
162+ // this also handles the case of empty cache where available is 0 but there are no entries.
153163 if (cache -> available == 0 ) {
154- if (type_cache_resize (type , cache ) == -1 ) {
164+ if (cache_resize (type , cache ) == -1 ) {
165+ // out of memory, don't cache the value
155166 return ;
156167 }
157- cache = get_cache (type );
168+ cache = cache_get (type );
158169 assert (cache -> available > 0 );
159170 }
160- type_cache_insert (cache , name , value );
171+ cache_insert (cache , name , value );
161172 FT_ATOMIC_STORE_UINT_RELAXED (cache -> version_tag , FT_ATOMIC_LOAD_UINT_RELAXED (type -> tp_version_tag ));
162173}
163174
164175struct _PyTypeCacheLookupResult _PyTypeCache_Lookup (PyTypeObject * type , PyObject * name )
165176{
166177 assert (PyUnicode_CheckExact (name ) && PyUnicode_CHECK_INTERNED (name ));
167178 struct _PyTypeCacheLookupResult miss = {PyStackRef_NULL , 0 , 0 };
168- struct type_cache * cache = get_cache (type );
179+ struct type_cache * cache = cache_get (type );
169180 if (cache == NULL ) {
170181 return miss ;
171182 }
@@ -202,7 +213,8 @@ struct _PyTypeCacheLookupResult _PyTypeCache_Lookup(PyTypeObject *type, PyObject
202213
203214
204215void _PyTypeCache_Invalidate (PyTypeObject * type ) {
205- struct type_cache * cache = get_cache (type );
206- set_cache (type , & empty_cache );
207- free_cache_delayed (cache );
216+ struct type_cache * cache = cache_get (type );
217+ // if the type was modified, the cache is set to the empty cache and the old cache is freed after a delay.
218+ cache_set (type , & empty_cache );
219+ cache_free_delayed (cache );
208220}
0 commit comments