Skip to content

Commit 36ffc5c

Browse files
implement per type method cache
1 parent f621ba1 commit 36ffc5c

15 files changed

Lines changed: 314 additions & 205 deletions

File tree

Include/cpython/object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ struct _typeobject {
246246
* This function must escape to any code that can result in
247247
* the GC being run, such as Py_DECREF. */
248248
_Py_iteritemfunc _tp_iteritem;
249+
250+
void *_tp_cache;
249251
};
250252

251253
#define _Py_ATTR_CACHE_UNUSED (30000) // (see tp_versions_used)

Include/internal/pycore_interp_structs.h

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -548,23 +548,6 @@ struct _types_runtime_state {
548548
};
549549

550550

551-
// Type attribute lookup cache: speed up attribute and method lookups,
552-
// see _PyType_Lookup().
553-
struct type_cache_entry {
554-
unsigned int version; // initialized from type->tp_version_tag
555-
#ifdef Py_GIL_DISABLED
556-
_PySeqLock sequence;
557-
#endif
558-
PyObject *name; // reference to exactly a str or None
559-
PyObject *value; // borrowed reference or NULL
560-
};
561-
562-
#define MCACHE_SIZE_EXP 12
563-
564-
struct type_cache {
565-
struct type_cache_entry hashtable[1 << MCACHE_SIZE_EXP];
566-
};
567-
568551
typedef struct {
569552
PyTypeObject *type;
570553
int isbuiltin;
@@ -579,6 +562,10 @@ typedef struct {
579562
are also some diagnostic uses for the list of weakrefs,
580563
so we still keep it. */
581564
PyObject *tp_weaklist;
565+
/* Per-interpreter attribute lookup cache (struct type_cache *).
566+
For static builtin types the cache must be per-interpreter
567+
because tp_dict and the values it stores are per-interpreter. */
568+
void *_tp_cache;
582569
} managed_static_type_state;
583570

584571
#define TYPE_VERSION_CACHE_SIZE (1<<12) /* Must be a power of 2 */
@@ -589,8 +576,6 @@ struct types_state {
589576
where all those lower numbers are used for core static types. */
590577
unsigned int next_version_tag;
591578

592-
struct type_cache type_cache;
593-
594579
/* Every static builtin type is initialized for each interpreter
595580
during its own initialization, including for the main interpreter
596581
during global runtime initialization. This is done by calling

Include/internal/pycore_object.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,6 @@ _PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
291291
return ((type->tp_flags) & feature) != 0;
292292
}
293293

294-
extern void _PyType_InitCache(PyInterpreterState *interp);
295-
296294
extern PyStatus _PyObject_InitState(PyInterpreterState *interp);
297295
extern void _PyObject_FiniState(PyInterpreterState *interp);
298296
extern bool _PyRefchain_IsTraced(PyInterpreterState *interp, PyObject *obj);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#ifndef PY_INTERNAL_TYPECACHE_H
2+
#define PY_INTERNAL_TYPECACHE_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
#ifndef Py_BUILD_CORE
8+
# error "this header requires Py_BUILD_CORE define"
9+
#endif
10+
11+
#include "pycore_stackref.h"
12+
13+
14+
#define _Py_TYPECACHE_MINSIZE 8
15+
16+
struct type_cache_entry {
17+
PyObject *name;
18+
PyObject *value;
19+
};
20+
21+
struct type_cache {
22+
uint32_t mask;
23+
uint32_t version_tag;
24+
uint32_t available;
25+
uint32_t used;
26+
struct type_cache_entry hashtable[0];
27+
};
28+
29+
struct _PyTypeCacheLookupResult {
30+
_PyStackRef value;
31+
int cache_hit;
32+
uint32_t version_tag;
33+
};
34+
35+
36+
extern void _PyTypeCache_InitType(PyTypeObject *type);
37+
extern void _PyTypeCache_Insert(PyTypeObject *type, PyObject *name, PyObject *value);
38+
extern struct _PyTypeCacheLookupResult _PyTypeCache_Lookup(PyTypeObject *type, PyObject *name);
39+
extern void _PyTypeCache_Invalidate(PyTypeObject *type);
40+
41+
#ifdef __cplusplus
42+
}
43+
#endif
44+
#endif /* PY_INTERNAL_TYPECACHE_H */

Lib/test/test_free_threading/test_type.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,24 @@ def reader_func():
8484

8585
self.run_one(writer_func, reader_func)
8686

87+
def test_attr_cache_mortal(self):
88+
class C:
89+
x = object()
90+
91+
class D(C):
92+
pass
93+
94+
def writer_func():
95+
for _ in range(3000):
96+
C.x = object()
97+
98+
def reader_func():
99+
for _ in range(3000):
100+
C.x
101+
D.x
102+
103+
self.run_one(writer_func, reader_func)
104+
87105
def test___class___modification(self):
88106
loops = 200
89107

Lib/test/test_sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ def delx(self): del self.__x
17881788
check((1,2,3), vsize('') + self.P + 3*self.P)
17891789
# type
17901790
# static type: PyTypeObject
1791-
fmt = 'P2nPI13Pl4Pn9Pn12PI2Pc'
1791+
fmt = 'P2nPI13Pl4Pn9Pn12PI2PcP'
17921792
s = vsize(fmt)
17931793
check(int, s)
17941794
typeid = 'n' if support.Py_GIL_DISABLED else ''

Makefile.pre.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ PYTHON_OBJS= \
506506
Python/thread.o \
507507
Python/traceback.o \
508508
Python/tracemalloc.o \
509+
Python/typecache.o \
509510
Python/uniqueid.o \
510511
Python/getopt.o \
511512
Python/pystrcmp.o \
@@ -1411,6 +1412,7 @@ PYTHON_HEADERS= \
14111412
$(srcdir)/Include/internal/pycore_tracemalloc.h \
14121413
$(srcdir)/Include/internal/pycore_tstate.h \
14131414
$(srcdir)/Include/internal/pycore_tuple.h \
1415+
$(srcdir)/Include/internal/pycore_typecache.h \
14141416
$(srcdir)/Include/internal/pycore_typedefs.h \
14151417
$(srcdir)/Include/internal/pycore_typeobject.h \
14161418
$(srcdir)/Include/internal/pycore_typevarobject.h \

0 commit comments

Comments
 (0)