@@ -20,20 +20,13 @@ class object "PyObject *" "&PyBaseObject_Type"
20
20
21
21
#include "clinic/typeobject.c.h"
22
22
23
- /* bpo-40521: Type method cache is shared by all subinterpreters */
24
- #ifndef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
25
- # define MCACHE
26
- #endif
27
-
28
- #ifdef MCACHE
29
- /* Support type attribute cache */
23
+ /* Support type attribute lookup cache */
30
24
31
25
/* The cache can keep references to the names alive for longer than
32
26
they normally would. This is why the maximum size is limited to
33
27
MCACHE_MAX_ATTR_SIZE, since it might be a problem if very large
34
28
strings are used as attribute names. */
35
29
#define MCACHE_MAX_ATTR_SIZE 100
36
- #define MCACHE_SIZE_EXP 12
37
30
#define MCACHE_HASH (version , name_hash ) \
38
31
(((unsigned int)(version) ^ (unsigned int)(name_hash)) \
39
32
& ((1 << MCACHE_SIZE_EXP) - 1))
@@ -44,30 +37,16 @@ class object "PyObject *" "&PyBaseObject_Type"
44
37
#define MCACHE_CACHEABLE_NAME (name ) \
45
38
PyUnicode_CheckExact(name) && \
46
39
PyUnicode_IS_READY(name) && \
47
- PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE
48
-
49
- struct method_cache_entry {
50
- unsigned int version ;
51
- PyObject * name ; /* reference to exactly a str or None */
52
- PyObject * value ; /* borrowed */
53
- };
40
+ (PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE)
54
41
55
- static struct method_cache_entry method_cache [ 1 << MCACHE_SIZE_EXP ];
42
+ // Used to set PyTypeObject.tp_version_tag
56
43
static unsigned int next_version_tag = 0 ;
57
- #endif
58
44
59
45
typedef struct PySlot_Offset {
60
46
short subslot_offset ;
61
47
short slot_offset ;
62
48
} PySlot_Offset ;
63
49
64
- #define MCACHE_STATS 0
65
-
66
- #if MCACHE_STATS
67
- static size_t method_cache_hits = 0 ;
68
- static size_t method_cache_misses = 0 ;
69
- static size_t method_cache_collisions = 0 ;
70
- #endif
71
50
72
51
/* bpo-40521: Interned strings are shared by all subinterpreters */
73
52
#ifndef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
@@ -229,46 +208,93 @@ _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_d
229
208
return PyUnicode_FromStringAndSize (start , end - start );
230
209
}
231
210
232
- unsigned int
233
- PyType_ClearCache (void )
211
+
212
+ static struct type_cache *
213
+ get_type_cache (void )
234
214
{
235
- #ifdef MCACHE
236
- Py_ssize_t i ;
237
- unsigned int cur_version_tag = next_version_tag - 1 ;
215
+ PyInterpreterState * interp = _PyInterpreterState_GET ();
216
+ return & interp -> type_cache ;
217
+ }
218
+
238
219
220
+ static void
221
+ type_cache_clear (struct type_cache * cache , int use_none )
222
+ {
223
+ for (Py_ssize_t i = 0 ; i < (1 << MCACHE_SIZE_EXP ); i ++ ) {
224
+ struct type_cache_entry * entry = & cache -> hashtable [i ];
225
+ entry -> version = 0 ;
226
+ if (use_none ) {
227
+ // Set to None so _PyType_Lookup() can use Py_SETREF(),
228
+ // rather than using slower Py_XSETREF().
229
+ Py_XSETREF (entry -> name , Py_NewRef (Py_None ));
230
+ }
231
+ else {
232
+ Py_CLEAR (entry -> name );
233
+ }
234
+ entry -> value = NULL ;
235
+ }
236
+
237
+ // Mark all version tags as invalid
238
+ PyType_Modified (& PyBaseObject_Type );
239
+ }
240
+
241
+
242
+ void
243
+ _PyType_InitCache (PyInterpreterState * interp )
244
+ {
245
+ struct type_cache * cache = & interp -> type_cache ;
246
+ for (Py_ssize_t i = 0 ; i < (1 << MCACHE_SIZE_EXP ); i ++ ) {
247
+ struct type_cache_entry * entry = & cache -> hashtable [i ];
248
+ assert (entry -> name == NULL );
249
+
250
+ entry -> version = 0 ;
251
+ // Set to None so _PyType_Lookup() can use Py_SETREF(),
252
+ // rather than using slower Py_XSETREF().
253
+ entry -> name = Py_NewRef (Py_None );
254
+ entry -> value = NULL ;
255
+ }
256
+ }
257
+
258
+
259
+ static unsigned int
260
+ _PyType_ClearCache (struct type_cache * cache )
261
+ {
239
262
#if MCACHE_STATS
240
- size_t total = method_cache_hits + method_cache_collisions + method_cache_misses ;
263
+ size_t total = cache -> hits + cache -> collisions + cache -> misses ;
241
264
fprintf (stderr , "-- Method cache hits = %zd (%d%%)\n" ,
242
- method_cache_hits , (int ) (100.0 * method_cache_hits / total ));
265
+ cache -> hits , (int ) (100.0 * cache -> hits / total ));
243
266
fprintf (stderr , "-- Method cache true misses = %zd (%d%%)\n" ,
244
- method_cache_misses , (int ) (100.0 * method_cache_misses / total ));
267
+ cache -> misses , (int ) (100.0 * cache -> misses / total ));
245
268
fprintf (stderr , "-- Method cache collisions = %zd (%d%%)\n" ,
246
- method_cache_collisions , (int ) (100.0 * method_cache_collisions / total ));
269
+ cache -> collisions , (int ) (100.0 * cache -> collisions / total ));
247
270
fprintf (stderr , "-- Method cache size = %zd KiB\n" ,
248
- sizeof (method_cache ) / 1024 );
271
+ sizeof (cache -> hashtable ) / 1024 );
249
272
#endif
250
273
251
- for (i = 0 ; i < (1 << MCACHE_SIZE_EXP ); i ++ ) {
252
- method_cache [i ].version = 0 ;
253
- Py_CLEAR (method_cache [i ].name );
254
- method_cache [i ].value = NULL ;
255
- }
274
+ unsigned int cur_version_tag = next_version_tag - 1 ;
256
275
next_version_tag = 0 ;
257
- /* mark all version tags as invalid */
258
- PyType_Modified ( & PyBaseObject_Type );
276
+ type_cache_clear ( cache , 0 );
277
+
259
278
return cur_version_tag ;
260
- #else
261
- return 0 ;
262
- #endif
263
279
}
264
280
281
+
282
+ unsigned int
283
+ PyType_ClearCache (void )
284
+ {
285
+ struct type_cache * cache = get_type_cache ();
286
+ return _PyType_ClearCache (cache );
287
+ }
288
+
289
+
265
290
void
266
- _PyType_Fini (void )
291
+ _PyType_Fini (PyThreadState * tstate )
267
292
{
268
- PyType_ClearCache ( );
293
+ _PyType_ClearCache ( & tstate -> interp -> type_cache );
269
294
clear_slotdefs ();
270
295
}
271
296
297
+
272
298
void
273
299
PyType_Modified (PyTypeObject * type )
274
300
{
@@ -370,9 +396,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
370
396
Py_TPFLAGS_VALID_VERSION_TAG );
371
397
}
372
398
373
- #ifdef MCACHE
374
399
static int
375
- assign_version_tag (PyTypeObject * type )
400
+ assign_version_tag (struct type_cache * cache , PyTypeObject * type )
376
401
{
377
402
/* Ensure that the tp_version_tag is valid and set
378
403
Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this
@@ -393,31 +418,22 @@ assign_version_tag(PyTypeObject *type)
393
418
/* for stress-testing: next_version_tag &= 0xFF; */
394
419
395
420
if (type -> tp_version_tag == 0 ) {
396
- /* wrap-around or just starting Python - clear the whole
397
- cache by filling names with references to Py_None.
398
- Values are also set to NULL for added protection, as they
399
- are borrowed reference */
400
- for (i = 0 ; i < (1 << MCACHE_SIZE_EXP ); i ++ ) {
401
- method_cache [i ].value = NULL ;
402
- Py_INCREF (Py_None );
403
- Py_XSETREF (method_cache [i ].name , Py_None );
404
- }
405
- /* mark all version tags as invalid */
406
- PyType_Modified (& PyBaseObject_Type );
421
+ // Wrap-around or just starting Python - clear the whole cache
422
+ type_cache_clear (cache , 1 );
407
423
return 1 ;
408
424
}
425
+
409
426
bases = type -> tp_bases ;
410
427
n = PyTuple_GET_SIZE (bases );
411
428
for (i = 0 ; i < n ; i ++ ) {
412
429
PyObject * b = PyTuple_GET_ITEM (bases , i );
413
430
assert (PyType_Check (b ));
414
- if (!assign_version_tag ((PyTypeObject * )b ))
431
+ if (!assign_version_tag (cache , (PyTypeObject * )b ))
415
432
return 0 ;
416
433
}
417
434
type -> tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG ;
418
435
return 1 ;
419
436
}
420
- #endif
421
437
422
438
423
439
static PyMemberDef type_members [] = {
@@ -3316,20 +3332,19 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
3316
3332
PyObject * res ;
3317
3333
int error ;
3318
3334
3319
- #ifdef MCACHE
3320
3335
if (MCACHE_CACHEABLE_NAME (name ) &&
3321
3336
_PyType_HasFeature (type , Py_TPFLAGS_VALID_VERSION_TAG )) {
3322
3337
/* fast path */
3323
3338
unsigned int h = MCACHE_HASH_METHOD (type , name );
3324
- if (method_cache [h ].version == type -> tp_version_tag &&
3325
- method_cache [h ].name == name ) {
3339
+ struct type_cache * cache = get_type_cache ();
3340
+ struct type_cache_entry * entry = & cache -> hashtable [h ];
3341
+ if (entry -> version == type -> tp_version_tag && entry -> name == name ) {
3326
3342
#if MCACHE_STATS
3327
- method_cache_hits ++ ;
3343
+ cache -> hits ++ ;
3328
3344
#endif
3329
- return method_cache [ h ]. value ;
3345
+ return entry -> value ;
3330
3346
}
3331
3347
}
3332
- #endif
3333
3348
3334
3349
/* We may end up clearing live exceptions below, so make sure it's ours. */
3335
3350
assert (!PyErr_Occurred ());
@@ -3351,22 +3366,25 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
3351
3366
return NULL ;
3352
3367
}
3353
3368
3354
- #ifdef MCACHE
3355
- if (MCACHE_CACHEABLE_NAME (name ) && assign_version_tag (type )) {
3356
- unsigned int h = MCACHE_HASH_METHOD (type , name );
3357
- method_cache [h ].version = type -> tp_version_tag ;
3358
- method_cache [h ].value = res ; /* borrowed */
3359
- Py_INCREF (name );
3360
- assert (((PyASCIIObject * )(name ))-> hash != -1 );
3369
+ if (MCACHE_CACHEABLE_NAME (name )) {
3370
+ struct type_cache * cache = get_type_cache ();
3371
+ if (assign_version_tag (cache , type )) {
3372
+ unsigned int h = MCACHE_HASH_METHOD (type , name );
3373
+ struct type_cache_entry * entry = & cache -> hashtable [h ];
3374
+ entry -> version = type -> tp_version_tag ;
3375
+ entry -> value = res ; /* borrowed */
3376
+ assert (((PyASCIIObject * )(name ))-> hash != -1 );
3361
3377
#if MCACHE_STATS
3362
- if (method_cache [h ].name != Py_None && method_cache [h ].name != name )
3363
- method_cache_collisions ++ ;
3364
- else
3365
- method_cache_misses ++ ;
3378
+ if (entry -> name != Py_None && entry -> name != name ) {
3379
+ cache -> collisions ++ ;
3380
+ }
3381
+ else {
3382
+ cache -> misses ++ ;
3383
+ }
3366
3384
#endif
3367
- Py_SETREF (method_cache [h ].name , name );
3385
+ Py_SETREF (entry -> name , Py_NewRef (name ));
3386
+ }
3368
3387
}
3369
- #endif
3370
3388
return res ;
3371
3389
}
3372
3390
0 commit comments