Skip to content

Commit aa0e410

Browse files
authored
gh-153568: Use a managed dict for AST nodes (#160616)
AST nodes had an explicit `__dict__` slot, so every node built by `ast.parse` allocated its own dict. With `Py_TPFLAGS_MANAGED_DICT`, node attributes are stored as inline values with shared keys instead. `ast.parse` is ~15.8% faster, walking trees is ~15.9% faster, and the memory used by the AST trees is reduced by ~51.6%.
1 parent 2468c35 commit aa0e410

6 files changed

Lines changed: 23 additions & 27 deletions

File tree

‎Lib/test/test_ast/test_ast.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ def test_AST_objects(self):
103103
# "ast.AST constructor takes 0 positional arguments"
104104
ast.AST(2)
105105

106+
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
107+
def test_nodes_have_inline_values(self):
108+
tree = ast.parse("x = f(y, z=1)")
109+
for node in ast.walk(tree):
110+
with self.subTest(node=type(node).__name__):
111+
self.assertTrue(_testinternalcapi.has_inline_values(node))
112+
106113
def test_AST_fields_NULL_check(self):
107114
# See: https://github.com/python/cpython/issues/126105
108115
old_value = ast.AST._fields

‎Lib/test/test_sys.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2042,7 +2042,7 @@ def test_pythontypes(self):
20422042
check = self.check_sizeof
20432043
# _ast.AST
20442044
import _ast
2045-
check(_ast.Module(), size('3P'))
2045+
check(_ast.Module(), size('2P'))
20462046
try:
20472047
raise TypeError
20482048
except TypeError as e:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up :func:`ast.parse` and reduce the memory used by AST nodes by storing
2+
node attributes as inline values instead of a separate per-node dictionary.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Speed up :func:`ast.parse` by ~15.8% and reduce the memory used by ~51.6% by
2+
storing node attributes as inline values instead of in a per-node
3+
dictionary.

‎Parser/asdl_c.py‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,6 @@ def visitModule(self, mod):
913913
914914
typedef struct {
915915
PyObject_HEAD
916-
PyObject *dict;
917916
} AST_object;
918917
919918
static void
@@ -923,7 +922,7 @@ def visitModule(self, mod):
923922
/* bpo-31095: UnTrack is needed before calling any callbacks */
924923
PyTypeObject *tp = Py_TYPE(self);
925924
PyObject_GC_UnTrack(self);
926-
Py_CLEAR(self->dict);
925+
PyObject_ClearManagedDict(op);
927926
freefunc free_func = PyType_GetSlot(tp, Py_tp_free);
928927
assert(free_func != NULL);
929928
free_func(self);
@@ -935,15 +934,13 @@ def visitModule(self, mod):
935934
{
936935
AST_object *self = (AST_object*)op;
937936
Py_VISIT(Py_TYPE(self));
938-
Py_VISIT(self->dict);
939-
return 0;
937+
return PyObject_VisitManagedDict(op, visit, arg);
940938
}
941939
942940
static int
943941
ast_clear(PyObject *op)
944942
{
945-
AST_object *self = (AST_object*)op;
946-
Py_CLEAR(self->dict);
943+
PyObject_ClearManagedDict(op);
947944
return 0;
948945
}
949946
@@ -1419,11 +1416,6 @@ def visitModule(self, mod):
14191416
return result;
14201417
}
14211418
1422-
static PyMemberDef ast_type_members[] = {
1423-
{"__dictoffset__", Py_T_PYSSIZET, offsetof(AST_object, dict), Py_READONLY},
1424-
{NULL} /* Sentinel */
1425-
};
1426-
14271419
static PyMethodDef ast_type_methods[] = {
14281420
{"__reduce__", ast_type_reduce, METH_NOARGS, NULL},
14291421
{"__replace__", _PyCFunction_CAST(ast_type_replace), METH_VARARGS | METH_KEYWORDS,
@@ -1668,7 +1660,6 @@ def visitModule(self, mod):
16681660
{Py_tp_setattro, PyObject_GenericSetAttr},
16691661
{Py_tp_traverse, ast_traverse},
16701662
{Py_tp_clear, ast_clear},
1671-
{Py_tp_members, ast_type_members},
16721663
{Py_tp_methods, ast_type_methods},
16731664
{Py_tp_getset, ast_type_getsets},
16741665
{Py_tp_init, ast_type_init},
@@ -1682,7 +1673,8 @@ def visitModule(self, mod):
16821673
"ast.AST",
16831674
sizeof(AST_object),
16841675
0,
1685-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
1676+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC |
1677+
Py_TPFLAGS_MANAGED_DICT,
16861678
AST_type_slots
16871679
};
16881680

‎Python/Python-ast.c‎

Lines changed: 5 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)