Skip to content

Commit 4101018

Browse files
authored
bpo-42745: Make the type cache per-interpreter (GH-23947)
Make the type attribute lookup cache per-interpreter. Add private _PyType_InitCache() function, called by PyInterpreterState_New(). Continue to share next_version_tag between interpreters, since static types are still shared by interpreters. Remove MCACHE macro: the cache is no longer disabled if the EXPERIMENTAL_ISOLATED_SUBINTERPRETERS macro is defined.
1 parent 77fde8d commit 4101018

File tree

7 files changed

+128
-82
lines changed

7 files changed

+128
-82
lines changed

Include/internal/pycore_interp.h

+22
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,27 @@ struct atexit_state {
180180
};
181181

182182

183+
// Type attribute lookup cache: speed up attribute and method lookups,
184+
// see _PyType_Lookup().
185+
struct type_cache_entry {
186+
unsigned int version; // initialized from type->tp_version_tag
187+
PyObject *name; // reference to exactly a str or None
188+
PyObject *value; // borrowed reference or NULL
189+
};
190+
191+
#define MCACHE_SIZE_EXP 12
192+
#define MCACHE_STATS 0
193+
194+
struct type_cache {
195+
struct type_cache_entry hashtable[1 << MCACHE_SIZE_EXP];
196+
#if MCACHE_STATS
197+
size_t hits;
198+
size_t misses;
199+
size_t collisions;
200+
#endif
201+
};
202+
203+
183204
/* interpreter state */
184205

185206
#define _PY_NSMALLPOSINTS 257
@@ -284,6 +305,7 @@ struct _is {
284305
struct _Py_exc_state exc_state;
285306

286307
struct ast_state ast;
308+
struct type_cache type_cache;
287309
};
288310

289311
extern void _PyInterpreterState_ClearModules(PyInterpreterState *interp);

Include/internal/pycore_object.h

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ _PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
2727
return ((type->tp_flags & feature) != 0);
2828
}
2929

30+
extern void _PyType_InitCache(PyInterpreterState *interp);
31+
32+
3033
/* Inline functions trading binary compatibility for speed:
3134
_PyObject_Init() is the fast version of PyObject_Init(), and
3235
_PyObject_InitVar() is the fast version of PyObject_InitVar().

Include/internal/pycore_pylifecycle.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ extern void _PyExc_Fini(PyThreadState *tstate);
7676
extern void _PyImport_Fini(void);
7777
extern void _PyImport_Fini2(void);
7878
extern void _PyGC_Fini(PyThreadState *tstate);
79-
extern void _PyType_Fini(void);
79+
extern void _PyType_Fini(PyThreadState *tstate);
8080
extern void _Py_HashRandomization_Fini(void);
8181
extern void _PyUnicode_Fini(PyThreadState *tstate);
8282
extern void _PyUnicode_ClearInterned(PyThreadState *tstate);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make the type attribute lookup cache per-interpreter. Patch by Victor Stinner.

Objects/typeobject.c

+98-80
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,13 @@ class object "PyObject *" "&PyBaseObject_Type"
2020

2121
#include "clinic/typeobject.c.h"
2222

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 */
3024

3125
/* The cache can keep references to the names alive for longer than
3226
they normally would. This is why the maximum size is limited to
3327
MCACHE_MAX_ATTR_SIZE, since it might be a problem if very large
3428
strings are used as attribute names. */
3529
#define MCACHE_MAX_ATTR_SIZE 100
36-
#define MCACHE_SIZE_EXP 12
3730
#define MCACHE_HASH(version, name_hash) \
3831
(((unsigned int)(version) ^ (unsigned int)(name_hash)) \
3932
& ((1 << MCACHE_SIZE_EXP) - 1))
@@ -44,30 +37,16 @@ class object "PyObject *" "&PyBaseObject_Type"
4437
#define MCACHE_CACHEABLE_NAME(name) \
4538
PyUnicode_CheckExact(name) && \
4639
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)
5441

55-
static struct method_cache_entry method_cache[1 << MCACHE_SIZE_EXP];
42+
// Used to set PyTypeObject.tp_version_tag
5643
static unsigned int next_version_tag = 0;
57-
#endif
5844

5945
typedef struct PySlot_Offset {
6046
short subslot_offset;
6147
short slot_offset;
6248
} PySlot_Offset;
6349

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
7150

7251
/* bpo-40521: Interned strings are shared by all subinterpreters */
7352
#ifndef EXPERIMENTAL_ISOLATED_SUBINTERPRETERS
@@ -229,46 +208,93 @@ _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_d
229208
return PyUnicode_FromStringAndSize(start, end - start);
230209
}
231210

232-
unsigned int
233-
PyType_ClearCache(void)
211+
212+
static struct type_cache*
213+
get_type_cache(void)
234214
{
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+
238219

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+
{
239262
#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;
241264
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));
243266
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));
245268
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));
247270
fprintf(stderr, "-- Method cache size = %zd KiB\n",
248-
sizeof(method_cache) / 1024);
271+
sizeof(cache->hashtable) / 1024);
249272
#endif
250273

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;
256275
next_version_tag = 0;
257-
/* mark all version tags as invalid */
258-
PyType_Modified(&PyBaseObject_Type);
276+
type_cache_clear(cache, 0);
277+
259278
return cur_version_tag;
260-
#else
261-
return 0;
262-
#endif
263279
}
264280

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+
265290
void
266-
_PyType_Fini(void)
291+
_PyType_Fini(PyThreadState *tstate)
267292
{
268-
PyType_ClearCache();
293+
_PyType_ClearCache(&tstate->interp->type_cache);
269294
clear_slotdefs();
270295
}
271296

297+
272298
void
273299
PyType_Modified(PyTypeObject *type)
274300
{
@@ -370,9 +396,8 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
370396
Py_TPFLAGS_VALID_VERSION_TAG);
371397
}
372398

373-
#ifdef MCACHE
374399
static int
375-
assign_version_tag(PyTypeObject *type)
400+
assign_version_tag(struct type_cache *cache, PyTypeObject *type)
376401
{
377402
/* Ensure that the tp_version_tag is valid and set
378403
Py_TPFLAGS_VALID_VERSION_TAG. To respect the invariant, this
@@ -393,31 +418,22 @@ assign_version_tag(PyTypeObject *type)
393418
/* for stress-testing: next_version_tag &= 0xFF; */
394419

395420
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);
407423
return 1;
408424
}
425+
409426
bases = type->tp_bases;
410427
n = PyTuple_GET_SIZE(bases);
411428
for (i = 0; i < n; i++) {
412429
PyObject *b = PyTuple_GET_ITEM(bases, i);
413430
assert(PyType_Check(b));
414-
if (!assign_version_tag((PyTypeObject *)b))
431+
if (!assign_version_tag(cache, (PyTypeObject *)b))
415432
return 0;
416433
}
417434
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
418435
return 1;
419436
}
420-
#endif
421437

422438

423439
static PyMemberDef type_members[] = {
@@ -3316,20 +3332,19 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
33163332
PyObject *res;
33173333
int error;
33183334

3319-
#ifdef MCACHE
33203335
if (MCACHE_CACHEABLE_NAME(name) &&
33213336
_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
33223337
/* fast path */
33233338
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) {
33263342
#if MCACHE_STATS
3327-
method_cache_hits++;
3343+
cache->hits++;
33283344
#endif
3329-
return method_cache[h].value;
3345+
return entry->value;
33303346
}
33313347
}
3332-
#endif
33333348

33343349
/* We may end up clearing live exceptions below, so make sure it's ours. */
33353350
assert(!PyErr_Occurred());
@@ -3351,22 +3366,25 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
33513366
return NULL;
33523367
}
33533368

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);
33613377
#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+
}
33663384
#endif
3367-
Py_SETREF(method_cache[h].name, name);
3385+
Py_SETREF(entry->name, Py_NewRef(name));
3386+
}
33683387
}
3369-
#endif
33703388
return res;
33713389
}
33723390

Python/pylifecycle.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,7 @@ Py_FinalizeEx(void)
17501750
_PyImport_Fini();
17511751

17521752
/* Cleanup typeobject.c's internal caches. */
1753-
_PyType_Fini();
1753+
_PyType_Fini(tstate);
17541754

17551755
/* unload faulthandler module */
17561756
_PyFaulthandler_Fini();

Python/pystate.c

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Python.h"
55
#include "pycore_ceval.h"
66
#include "pycore_initconfig.h"
7+
#include "pycore_object.h" // _PyType_InitCache()
78
#include "pycore_pyerrors.h"
89
#include "pycore_pylifecycle.h"
910
#include "pycore_pymem.h" // _PyMem_SetDefaultAllocator()
@@ -223,6 +224,7 @@ PyInterpreterState_New(void)
223224

224225
_PyGC_InitState(&interp->gc);
225226
PyConfig_InitPythonConfig(&interp->config);
227+
_PyType_InitCache(interp);
226228

227229
interp->eval_frame = _PyEval_EvalFrameDefault;
228230
#ifdef HAVE_DLOPEN

0 commit comments

Comments
 (0)