Skip to content

Commit e0ce3e3

Browse files
[mypyc] feat: __mypyc_empty_tuple__ constant (#19654)
I realized that any time a user has a kwarg-only call expression like `fn(abc=123, ...)` in their compiled code, and `func` is not a native function, a new empty tuple is created every time This is not really necessary, we can just hold the same empty tuple in memory as a constant and pass it around. It's immutable, and that's already what we're already doing, since `tuple() is tuple()` but our current method involves more steps. This should slightly improve the speed of kwarg-only python func calling.
1 parent e852829 commit e0ce3e3

File tree

7 files changed

+51
-16
lines changed

7 files changed

+51
-16
lines changed

mypyc/codegen/emit.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,17 +1036,21 @@ def emit_box(
10361036
self.emit_line(f"{declaration}{dest} = PyFloat_FromDouble({src});")
10371037
elif isinstance(typ, RTuple):
10381038
self.declare_tuple_struct(typ)
1039-
self.emit_line(f"{declaration}{dest} = PyTuple_New({len(typ.types)});")
1040-
self.emit_line(f"if (unlikely({dest} == NULL))")
1041-
self.emit_line(" CPyError_OutOfMemory();")
1042-
# TODO: Fail if dest is None
1043-
for i in range(len(typ.types)):
1044-
if not typ.is_unboxed:
1045-
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {src}.f{i}")
1046-
else:
1047-
inner_name = self.temp_name()
1048-
self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True)
1049-
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});")
1039+
if not typ.types:
1040+
self.emit_line(f"{declaration}{dest} = CPyTuple_LoadEmptyTupleConstant();")
1041+
else:
1042+
self.emit_line(f"{declaration}{dest} = PyTuple_New({len(typ.types)});")
1043+
self.emit_line(f"if (unlikely({dest} == NULL))")
1044+
self.emit_line(" CPyError_OutOfMemory();")
1045+
1046+
# TODO: Fail if dest is None
1047+
for i in range(len(typ.types)):
1048+
if not typ.is_unboxed:
1049+
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {src}.f{i}")
1050+
else:
1051+
inner_name = self.temp_name()
1052+
self.emit_box(f"{src}.f{i}", inner_name, typ.types[i], declare_dest=True)
1053+
self.emit_line(f"PyTuple_SET_ITEM({dest}, {i}, {inner_name});")
10501054
else:
10511055
assert not typ.is_unboxed
10521056
# Type is boxed -- trivially just assign.

mypyc/irbuild/ll_builder.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
)
190190
from mypyc.primitives.tuple_ops import (
191191
list_tuple_op,
192+
load_empty_tuple_constant_op,
192193
new_tuple_op,
193194
new_tuple_with_length_op,
194195
sequence_tuple_op,
@@ -2362,8 +2363,11 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val
23622363
return self.call_c(generic_len_op, [val], line)
23632364

23642365
def new_tuple(self, items: list[Value], line: int) -> Value:
2365-
size: Value = Integer(len(items), c_pyssize_t_rprimitive)
2366-
return self.call_c(new_tuple_op, [size] + items, line)
2366+
if items:
2367+
size: Value = Integer(len(items), c_pyssize_t_rprimitive)
2368+
return self.call_c(new_tuple_op, [size] + items, line)
2369+
else:
2370+
return self.call_c(load_empty_tuple_constant_op, [], line)
23672371

23682372
def new_tuple_with_length(self, length: Value, line: int) -> Value:
23692373
"""This function returns an uninitialized tuple.

mypyc/lib-rt/CPy.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ typedef struct tuple_T4CIOO {
6464
} tuple_T4CIOO;
6565
#endif
6666

67+
// System-wide empty tuple constant
68+
extern PyObject * __mypyc_empty_tuple__;
69+
70+
static inline PyObject *CPyTuple_LoadEmptyTupleConstant() {
71+
#if !CPY_3_12_FEATURES
72+
Py_INCREF(__mypyc_empty_tuple__);
73+
#endif
74+
return __mypyc_empty_tuple__;
75+
}
6776

6877
// Native object operations
6978

mypyc/lib-rt/init.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,21 @@
44
struct ExcDummyStruct _CPy_ExcDummyStruct = { PyObject_HEAD_INIT(NULL) };
55
PyObject *_CPy_ExcDummy = (PyObject *)&_CPy_ExcDummyStruct;
66

7+
// System-wide empty tuple constant
8+
PyObject * __mypyc_empty_tuple__ = NULL;
9+
710
// Because its dynamic linker is more restricted than linux/OS X,
811
// Windows doesn't allow initializing globals with values from
912
// other dynamic libraries. This means we need to initialize
1013
// things at load time.
1114
void CPy_Init(void) {
1215
_CPy_ExcDummyStruct.ob_base.ob_type = &PyBaseObject_Type;
16+
17+
// Initialize system-wide empty tuple constant
18+
if (__mypyc_empty_tuple__ == NULL) {
19+
__mypyc_empty_tuple__ = PyTuple_New(0);
20+
if (!__mypyc_empty_tuple__) {
21+
CPyError_OutOfMemory();
22+
}
23+
}
1324
}

mypyc/primitives/tuple_ops.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
error_kind=ERR_MAGIC,
5656
)
5757

58+
load_empty_tuple_constant_op = custom_op(
59+
arg_types=[],
60+
return_type=tuple_rprimitive,
61+
c_function_name="CPyTuple_LoadEmptyTupleConstant",
62+
error_kind=ERR_NEVER,
63+
)
64+
5865
# PyTuple_SET_ITEM does no error checking,
5966
# and should only be used to fill in brand new tuples.
6067
new_tuple_set_item_op = custom_op(

mypyc/test-data/irbuild-basic.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,7 @@ L0:
17501750
r7 = __main__.globals :: static
17511751
r8 = 'f'
17521752
r9 = CPyDict_GetItem(r7, r8)
1753-
r10 = PyTuple_Pack(0)
1753+
r10 = CPyTuple_LoadEmptyTupleConstant()
17541754
r11 = PyDict_Copy(r6)
17551755
r12 = PyObject_Call(r9, r10, r11)
17561756
r13 = unbox(tuple[int, int, int], r12)

mypyc/test-data/irbuild-classes.test

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ L2:
297297
r27 = CPyType_FromTemplate(r26, r24, r25)
298298
r28 = C_trait_vtable_setup()
299299
r29 = '__mypyc_attrs__'
300-
r30 = PyTuple_Pack(0)
300+
r30 = CPyTuple_LoadEmptyTupleConstant()
301301
r31 = PyObject_SetAttr(r27, r29, r30)
302302
r32 = r31 >= 0 :: signed
303303
__main__.C = r27 :: type
@@ -310,7 +310,7 @@ L2:
310310
r39 = __main__.S_template :: type
311311
r40 = CPyType_FromTemplate(r39, r37, r38)
312312
r41 = '__mypyc_attrs__'
313-
r42 = PyTuple_Pack(0)
313+
r42 = CPyTuple_LoadEmptyTupleConstant()
314314
r43 = PyObject_SetAttr(r40, r41, r42)
315315
r44 = r43 >= 0 :: signed
316316
__main__.S = r40 :: type

0 commit comments

Comments
 (0)