Skip to content

Commit c2e1607

Browse files
methanemiss-islington
authored andcommitted
bpo-34100: Merge constants recursively (GH-8341)
There are some same consts in a module. This commit merges them into single instance. It reduces number of objects in memory after loading modules. https://bugs.python.org/issue34100
1 parent f0b366a commit c2e1607

File tree

6 files changed

+4692
-4594
lines changed

6 files changed

+4692
-4594
lines changed

Lib/test/test_compile.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,16 @@ def check_same_constant(const):
615615
self.check_constant(f1, Ellipsis)
616616
self.assertEqual(repr(f1()), repr(Ellipsis))
617617

618+
# Merge constants in tuple or frozenset
619+
# NOTE: frozenset can't reuse previous const, but frozenset
620+
# item can be reused later.
621+
f3 = lambda x: x in {("not a name",)}
622+
f1, f2 = lambda: "not a name", lambda: ("not a name",)
623+
self.assertIs(next(iter(f3.__code__.co_consts[1])),
624+
f2.__code__.co_consts[1])
625+
self.assertIs(f1.__code__.co_consts[1],
626+
f2.__code__.co_consts[1][0])
627+
618628
# {0} is converted to a constant frozenset({0}) by the peephole
619629
# optimizer
620630
f1, f2 = lambda x: x in {0}, lambda x: x in {0}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Compiler now merges constants in tuples and frozensets recursively. Code
2+
attributes like ``co_names`` are merged too.

Python/compile.c

Lines changed: 161 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ struct compiler {
160160
int c_interactive; /* true if in interactive mode */
161161
int c_nestlevel;
162162

163+
PyObject *c_const_cache; /* Python dict holding all constants,
164+
including names tuple */
163165
struct compiler_unit *u; /* compiler state for current block */
164166
PyObject *c_stack; /* Python list holding compiler_unit ptrs */
165167
PyArena *c_arena; /* pointer to memory allocation arena */
@@ -285,9 +287,16 @@ compiler_init(struct compiler *c)
285287
{
286288
memset(c, 0, sizeof(struct compiler));
287289

290+
c->c_const_cache = PyDict_New();
291+
if (!c->c_const_cache) {
292+
return 0;
293+
}
294+
288295
c->c_stack = PyList_New(0);
289-
if (!c->c_stack)
296+
if (!c->c_stack) {
297+
Py_CLEAR(c->c_const_cache);
290298
return 0;
299+
}
291300

292301
return 1;
293302
}
@@ -387,6 +396,7 @@ compiler_free(struct compiler *c)
387396
if (c->c_future)
388397
PyObject_Free(c->c_future);
389398
Py_XDECREF(c->c_filename);
399+
Py_DECREF(c->c_const_cache);
390400
Py_DECREF(c->c_stack);
391401
}
392402

@@ -1179,18 +1189,121 @@ compiler_add_o(struct compiler *c, PyObject *dict, PyObject *o)
11791189
return arg;
11801190
}
11811191

1192+
// Merge const *o* recursively and return constant key object.
1193+
static PyObject*
1194+
merge_consts_recursive(struct compiler *c, PyObject *o)
1195+
{
1196+
// None and Ellipsis are singleton, and key is the singleton.
1197+
// No need to merge object and key.
1198+
if (o == Py_None || o == Py_Ellipsis) {
1199+
Py_INCREF(o);
1200+
return o;
1201+
}
1202+
1203+
PyObject *key = _PyCode_ConstantKey(o);
1204+
if (key == NULL) {
1205+
return NULL;
1206+
}
1207+
1208+
// t is borrowed reference
1209+
PyObject *t = PyDict_SetDefault(c->c_const_cache, key, key);
1210+
if (t != key) {
1211+
Py_INCREF(t);
1212+
Py_DECREF(key);
1213+
return t;
1214+
}
1215+
1216+
if (PyTuple_CheckExact(o)) {
1217+
Py_ssize_t i, len = PyTuple_GET_SIZE(o);
1218+
for (i = 0; i < len; i++) {
1219+
PyObject *item = PyTuple_GET_ITEM(o, i);
1220+
PyObject *u = merge_consts_recursive(c, item);
1221+
if (u == NULL) {
1222+
Py_DECREF(key);
1223+
return NULL;
1224+
}
1225+
1226+
// See _PyCode_ConstantKey()
1227+
PyObject *v; // borrowed
1228+
if (PyTuple_CheckExact(u)) {
1229+
v = PyTuple_GET_ITEM(u, 1);
1230+
}
1231+
else {
1232+
v = u;
1233+
}
1234+
if (v != item) {
1235+
Py_INCREF(v);
1236+
PyTuple_SET_ITEM(o, i, v);
1237+
Py_DECREF(item);
1238+
}
1239+
1240+
Py_DECREF(u);
1241+
}
1242+
}
1243+
else if (PyFrozenSet_CheckExact(o)) {
1244+
// We register items in the frozenset, but don't rewrite
1245+
// the frozenset when the item is already registered
1246+
// because frozenset is rare and difficult.
1247+
1248+
// *key* is tuple. And it's first item is frozenset of
1249+
// constant keys.
1250+
// See _PyCode_ConstantKey() for detail.
1251+
assert(PyTuple_CheckExact(key));
1252+
assert(PyTuple_GET_SIZE(key) == 2);
1253+
1254+
Py_ssize_t len = PySet_GET_SIZE(o);
1255+
if (len == 0) {
1256+
return key;
1257+
}
1258+
PyObject *tuple = PyTuple_New(len);
1259+
if (tuple == NULL) {
1260+
Py_DECREF(key);
1261+
return NULL;
1262+
}
1263+
Py_ssize_t i = 0, pos = 0;
1264+
PyObject *item;
1265+
Py_hash_t hash;
1266+
while (_PySet_NextEntry(o, &pos, &item, &hash)) {
1267+
PyObject *k = merge_consts_recursive(c, item);
1268+
if (k == NULL) {
1269+
Py_DECREF(tuple);
1270+
Py_DECREF(key);
1271+
return NULL;
1272+
}
1273+
PyObject *u;
1274+
if (PyTuple_CheckExact(k)) {
1275+
u = PyTuple_GET_ITEM(k, 1);
1276+
}
1277+
else {
1278+
u = k;
1279+
}
1280+
Py_INCREF(u);
1281+
PyTuple_SET_ITEM(tuple, i, u);
1282+
i++;
1283+
}
1284+
1285+
PyObject *new = PyFrozenSet_New(tuple);
1286+
Py_DECREF(tuple);
1287+
if (new == NULL) {
1288+
Py_DECREF(key);
1289+
return NULL;
1290+
}
1291+
PyTuple_SET_ITEM(key, 1, new);
1292+
}
1293+
1294+
return key;
1295+
}
1296+
11821297
static Py_ssize_t
11831298
compiler_add_const(struct compiler *c, PyObject *o)
11841299
{
1185-
PyObject *t;
1186-
Py_ssize_t arg;
1187-
1188-
t = _PyCode_ConstantKey(o);
1189-
if (t == NULL)
1300+
PyObject *key = merge_consts_recursive(c, o);
1301+
if (key == NULL) {
11901302
return -1;
1303+
}
11911304

1192-
arg = compiler_add_o(c, c->u->u_consts, t);
1193-
Py_DECREF(t);
1305+
Py_ssize_t arg = compiler_add_o(c, c->u->u_consts, key);
1306+
Py_DECREF(key);
11941307
return arg;
11951308
}
11961309

@@ -5380,6 +5493,35 @@ compute_code_flags(struct compiler *c)
53805493
return flags;
53815494
}
53825495

5496+
// Merge *tuple* with constant cache.
5497+
// Unlike merge_consts_recursive(), this function doesn't work recursively.
5498+
static int
5499+
merge_const_tuple(struct compiler *c, PyObject **tuple)
5500+
{
5501+
assert(PyTuple_CheckExact(*tuple));
5502+
5503+
PyObject *key = _PyCode_ConstantKey(*tuple);
5504+
if (key == NULL) {
5505+
return 0;
5506+
}
5507+
5508+
// t is borrowed reference
5509+
PyObject *t = PyDict_SetDefault(c->c_const_cache, key, key);
5510+
Py_DECREF(key);
5511+
if (t == NULL) {
5512+
return 0;
5513+
}
5514+
if (t == key) { // tuple is new constant.
5515+
return 1;
5516+
}
5517+
5518+
PyObject *u = PyTuple_GET_ITEM(t, 1);
5519+
Py_INCREF(u);
5520+
Py_DECREF(*tuple);
5521+
*tuple = u;
5522+
return 1;
5523+
}
5524+
53835525
static PyCodeObject *
53845526
makecode(struct compiler *c, struct assembler *a)
53855527
{
@@ -5410,6 +5552,14 @@ makecode(struct compiler *c, struct assembler *a)
54105552
if (!freevars)
54115553
goto error;
54125554

5555+
if (!merge_const_tuple(c, &names) ||
5556+
!merge_const_tuple(c, &varnames) ||
5557+
!merge_const_tuple(c, &cellvars) ||
5558+
!merge_const_tuple(c, &freevars))
5559+
{
5560+
goto error;
5561+
}
5562+
54135563
nlocals = PyDict_GET_SIZE(c->u->u_varnames);
54145564
assert(nlocals < INT_MAX);
54155565
nlocals_int = Py_SAFE_DOWNCAST(nlocals, Py_ssize_t, int);
@@ -5427,6 +5577,9 @@ makecode(struct compiler *c, struct assembler *a)
54275577
goto error;
54285578
Py_DECREF(consts);
54295579
consts = tmp;
5580+
if (!merge_const_tuple(c, &consts)) {
5581+
goto error;
5582+
}
54305583

54315584
argcount = Py_SAFE_DOWNCAST(c->u->u_argcount, Py_ssize_t, int);
54325585
kwonlyargcount = Py_SAFE_DOWNCAST(c->u->u_kwonlyargcount, Py_ssize_t, int);

0 commit comments

Comments
 (0)