Skip to content

Commit 74bb3ca

Browse files
gh-142863: optimize list and set calls with generator expressions (#142864)
1 parent e79c9b7 commit 74bb3ca

File tree

8 files changed

+75
-19
lines changed

8 files changed

+75
-19
lines changed

Include/internal/pycore_magic_number.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ Known values:
288288
Python 3.15a1 3655 (Fix miscompilation of some module-level annotations)
289289
Python 3.15a2 3656 (Add TRACE_RECORD instruction, for platforms with switch based interpreter)
290290
Python 3.15a4 3657 (Add BINARY_OP_SUBSCR_USTR_INT)
291+
Python 3.15a4 3658 (Optimize bytecode for list/set called on genexp)
291292
292293
293294
Python 3.16 will start with 3700
@@ -301,7 +302,7 @@ PC/launcher.c must also be updated.
301302
302303
*/
303304

304-
#define PYC_MAGIC_NUMBER 3657
305+
#define PYC_MAGIC_NUMBER 3658
305306
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
306307
(little-endian) and then appending b'\r\n'. */
307308
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_opcode_utils.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ extern "C" {
7373
#define CONSTANT_BUILTIN_TUPLE 2
7474
#define CONSTANT_BUILTIN_ALL 3
7575
#define CONSTANT_BUILTIN_ANY 4
76-
#define NUM_COMMON_CONSTANTS 5
76+
#define CONSTANT_BUILTIN_LIST 5
77+
#define CONSTANT_BUILTIN_SET 6
78+
#define NUM_COMMON_CONSTANTS 7
7779

7880
/* Values used in the oparg for RESUME */
7981
#define RESUME_AT_FUNC_START 0

Lib/opcode.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
_intrinsic_2_descs = _opcode.get_intrinsic2_descs()
4141
_special_method_names = _opcode.get_special_method_names()
4242
_common_constants = [builtins.AssertionError, builtins.NotImplementedError,
43-
builtins.tuple, builtins.all, builtins.any]
43+
builtins.tuple, builtins.all, builtins.any, builtins.list,
44+
builtins.set]
4445
_nb_ops = _opcode.get_nb_ops()
4546

4647
hascompare = [opmap["COMPARE_OP"]]

Lib/test/test_builtin.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def test_any(self):
246246
S = [10, 20, 30]
247247
self.assertEqual(any(x > 42 for x in S), False)
248248

249-
def test_all_any_tuple_optimization(self):
249+
def test_all_any_tuple_list_set_optimization(self):
250250
def f_all():
251251
return all(x-2 for x in [1,2,3])
252252

@@ -256,7 +256,13 @@ def f_any():
256256
def f_tuple():
257257
return tuple(2*x for x in [1,2,3])
258258

259-
funcs = [f_all, f_any, f_tuple]
259+
def f_list():
260+
return list(2*x for x in [1,2,3])
261+
262+
def f_set():
263+
return set(2*x for x in [1,2,3])
264+
265+
funcs = [f_all, f_any, f_tuple, f_list, f_set]
260266

261267
for f in funcs:
262268
# check that generator code object is not duplicated
@@ -266,33 +272,35 @@ def f_tuple():
266272

267273
# check the overriding the builtins works
268274

269-
global all, any, tuple
270-
saved = all, any, tuple
275+
global all, any, tuple, list, set
276+
saved = all, any, tuple, list, set
271277
try:
272278
all = lambda x : "all"
273279
any = lambda x : "any"
274280
tuple = lambda x : "tuple"
281+
list = lambda x : "list"
282+
set = lambda x : "set"
275283

276284
overridden_outputs = [f() for f in funcs]
277285
finally:
278-
all, any, tuple = saved
279-
280-
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple'])
286+
all, any, tuple, list, set = saved
281287

288+
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set'])
282289
# Now repeat, overriding the builtins module as well
283-
saved = all, any, tuple
290+
saved = all, any, tuple, list, set
284291
try:
285292
builtins.all = all = lambda x : "all"
286293
builtins.any = any = lambda x : "any"
287294
builtins.tuple = tuple = lambda x : "tuple"
295+
builtins.list = list = lambda x : "list"
296+
builtins.set = set = lambda x : "set"
288297

289298
overridden_outputs = [f() for f in funcs]
290299
finally:
291-
all, any, tuple = saved
292-
builtins.all, builtins.any, builtins.tuple = saved
293-
294-
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple'])
300+
all, any, tuple, list, set = saved
301+
builtins.all, builtins.any, builtins.tuple, builtins.list, builtins.set = saved
295302

303+
self.assertEqual(overridden_outputs, ['all', 'any', 'tuple', 'list', 'set'])
296304

297305
def test_ascii(self):
298306
self.assertEqual(ascii(''), '\'\'')

Lib/test/test_dis.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,14 +827,36 @@ def foo(x):
827827
828828
%4d RESUME 0
829829
830-
%4d LOAD_GLOBAL 1 (list + NULL)
830+
%4d LOAD_GLOBAL 0 (list)
831+
COPY 1
832+
LOAD_COMMON_CONSTANT 5 (list)
833+
IS_OP 0 (is)
834+
POP_JUMP_IF_FALSE 22 (to L3)
835+
NOT_TAKEN
836+
POP_TOP
837+
BUILD_LIST 0
831838
LOAD_FAST_BORROW 0 (x)
832839
BUILD_TUPLE 1
833840
LOAD_CONST %d (<code object <genexpr> at 0x..., file "%s", line %d>)
834841
MAKE_FUNCTION
835842
SET_FUNCTION_ATTRIBUTE 8 (closure)
836843
LOAD_DEREF 1 (y)
837844
CALL 0
845+
PUSH_NULL
846+
L1: FOR_ITER 3 (to L2)
847+
LIST_APPEND 3
848+
JUMP_BACKWARD 5 (to L1)
849+
L2: END_FOR
850+
POP_ITER
851+
RETURN_VALUE
852+
L3: PUSH_NULL
853+
LOAD_FAST_BORROW 0 (x)
854+
BUILD_TUPLE 1
855+
LOAD_CONST 1 (<code object <genexpr> at 0x..., file "%s", line %d>)
856+
MAKE_FUNCTION
857+
SET_FUNCTION_ATTRIBUTE 8 (closure)
858+
LOAD_DEREF 1 (y)
859+
CALL 0
838860
CALL 1
839861
RETURN_VALUE
840862
""" % (dis_nested_0,
@@ -845,6 +867,8 @@ def foo(x):
845867
1 if __debug__ else 0,
846868
__file__,
847869
_h.__code__.co_firstlineno + 3,
870+
__file__,
871+
_h.__code__.co_firstlineno + 3,
848872
)
849873

850874
dis_nested_2 = """%s
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generate optimized bytecode when calling :class:`list` or :class:`set` with generator expression.

Python/codegen.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3892,15 +3892,23 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
38923892
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "tuple")) {
38933893
const_oparg = CONSTANT_BUILTIN_TUPLE;
38943894
}
3895+
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "list")) {
3896+
const_oparg = CONSTANT_BUILTIN_LIST;
3897+
}
3898+
else if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "set")) {
3899+
const_oparg = CONSTANT_BUILTIN_SET;
3900+
}
38953901
if (const_oparg != -1) {
38963902
ADDOP_I(c, loc, COPY, 1); // the function
38973903
ADDOP_I(c, loc, LOAD_COMMON_CONSTANT, const_oparg);
38983904
ADDOP_COMPARE(c, loc, Is);
38993905
ADDOP_JUMP(c, loc, POP_JUMP_IF_FALSE, skip_optimization);
39003906
ADDOP(c, loc, POP_TOP);
39013907

3902-
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
3908+
if (const_oparg == CONSTANT_BUILTIN_TUPLE || const_oparg == CONSTANT_BUILTIN_LIST) {
39033909
ADDOP_I(c, loc, BUILD_LIST, 0);
3910+
} else if (const_oparg == CONSTANT_BUILTIN_SET) {
3911+
ADDOP_I(c, loc, BUILD_SET, 0);
39043912
}
39053913
expr_ty generator_exp = asdl_seq_GET(args, 0);
39063914
VISIT(c, expr, generator_exp);
@@ -3911,17 +3919,22 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
39113919
ADDOP(c, loc, PUSH_NULL); // Push NULL index for loop
39123920
USE_LABEL(c, loop);
39133921
ADDOP_JUMP(c, loc, FOR_ITER, cleanup);
3914-
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
3922+
if (const_oparg == CONSTANT_BUILTIN_TUPLE || const_oparg == CONSTANT_BUILTIN_LIST) {
39153923
ADDOP_I(c, loc, LIST_APPEND, 3);
39163924
ADDOP_JUMP(c, loc, JUMP, loop);
3925+
} else if (const_oparg == CONSTANT_BUILTIN_SET) {
3926+
ADDOP_I(c, loc, SET_ADD, 3);
3927+
ADDOP_JUMP(c, loc, JUMP, loop);
39173928
}
39183929
else {
39193930
ADDOP(c, loc, TO_BOOL);
39203931
ADDOP_JUMP(c, loc, continue_jump_opcode, loop);
39213932
}
39223933

39233934
ADDOP(c, NO_LOCATION, POP_ITER);
3924-
if (const_oparg != CONSTANT_BUILTIN_TUPLE) {
3935+
if (const_oparg != CONSTANT_BUILTIN_TUPLE &&
3936+
const_oparg != CONSTANT_BUILTIN_LIST &&
3937+
const_oparg != CONSTANT_BUILTIN_SET) {
39253938
ADDOP_LOAD_CONST(c, loc, initial_res == Py_True ? Py_False : Py_True);
39263939
}
39273940
ADDOP_JUMP(c, loc, JUMP, end);
@@ -3931,6 +3944,10 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end)
39313944
ADDOP(c, NO_LOCATION, POP_ITER);
39323945
if (const_oparg == CONSTANT_BUILTIN_TUPLE) {
39333946
ADDOP_I(c, loc, CALL_INTRINSIC_1, INTRINSIC_LIST_TO_TUPLE);
3947+
} else if (const_oparg == CONSTANT_BUILTIN_LIST) {
3948+
// result is already a list
3949+
} else if (const_oparg == CONSTANT_BUILTIN_SET) {
3950+
// result is already a set
39343951
}
39353952
else {
39363953
ADDOP_LOAD_CONST(c, loc, initial_res);

Python/pylifecycle.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,6 +832,8 @@ pycore_init_builtins(PyThreadState *tstate)
832832
interp->common_consts[CONSTANT_BUILTIN_TUPLE] = (PyObject*)&PyTuple_Type;
833833
interp->common_consts[CONSTANT_BUILTIN_ALL] = all;
834834
interp->common_consts[CONSTANT_BUILTIN_ANY] = any;
835+
interp->common_consts[CONSTANT_BUILTIN_LIST] = (PyObject*)&PyList_Type;
836+
interp->common_consts[CONSTANT_BUILTIN_SET] = (PyObject*)&PySet_Type;
835837

836838
for (int i=0; i < NUM_COMMON_CONSTANTS; i++) {
837839
assert(interp->common_consts[i] != NULL);

0 commit comments

Comments
 (0)