Skip to content

Commit 8c22eba

Browse files
authored
gh-90370: Argument Clinic: avoid temporary tuple creation for varargs (#126064)
Avoid temporary tuple creation when all arguments either positional-only or vararg. Objects/setobject.c and Modules/gcmodule.c adapted. This fixes slight performance regression for set methods, introduced by gh-115112.
1 parent d07dcce commit 8c22eba

File tree

11 files changed

+209
-222
lines changed

11 files changed

+209
-222
lines changed

Lib/test/clinic.test.c

+21-27
Original file line numberDiff line numberDiff line change
@@ -4148,36 +4148,32 @@ PyDoc_STRVAR(test_vararg_and_posonly__doc__,
41484148
{"test_vararg_and_posonly", _PyCFunction_CAST(test_vararg_and_posonly), METH_FASTCALL, test_vararg_and_posonly__doc__},
41494149

41504150
static PyObject *
4151-
test_vararg_and_posonly_impl(PyObject *module, PyObject *a, PyObject *args);
4151+
test_vararg_and_posonly_impl(PyObject *module, PyObject *a, Py_ssize_t nargs,
4152+
PyObject *const *args);
41524153

41534154
static PyObject *
41544155
test_vararg_and_posonly(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
41554156
{
41564157
PyObject *return_value = NULL;
4158+
Py_ssize_t nvararg = nargs - 1;
41574159
PyObject *a;
4158-
PyObject *__clinic_args = NULL;
4160+
PyObject *const *__clinic_args = NULL;
41594161

41604162
if (!_PyArg_CheckPositional("test_vararg_and_posonly", nargs, 1, PY_SSIZE_T_MAX)) {
41614163
goto exit;
41624164
}
41634165
a = args[0];
4164-
__clinic_args = PyTuple_New(nargs - 1);
4165-
if (!__clinic_args) {
4166-
goto exit;
4167-
}
4168-
for (Py_ssize_t i = 0; i < nargs - 1; ++i) {
4169-
PyTuple_SET_ITEM(__clinic_args, i, Py_NewRef(args[1 + i]));
4170-
}
4171-
return_value = test_vararg_and_posonly_impl(module, a, __clinic_args);
4166+
__clinic_args = args + 1;
4167+
return_value = test_vararg_and_posonly_impl(module, a, nvararg, __clinic_args);
41724168

41734169
exit:
4174-
Py_XDECREF(__clinic_args);
41754170
return return_value;
41764171
}
41774172

41784173
static PyObject *
4179-
test_vararg_and_posonly_impl(PyObject *module, PyObject *a, PyObject *args)
4180-
/*[clinic end generated code: output=79b75dc07decc8d6 input=9cfa748bbff09877]*/
4174+
test_vararg_and_posonly_impl(PyObject *module, PyObject *a, Py_ssize_t nargs,
4175+
PyObject *const *args)
4176+
/*[clinic end generated code: output=dc2dd9483cc0459e input=9cfa748bbff09877]*/
41814177

41824178
/*[clinic input]
41834179
test_vararg
@@ -4931,14 +4927,14 @@ PyDoc_STRVAR(Test___init____doc__,
49314927
"Varargs init method. For example, nargs is translated to PyTuple_GET_SIZE.");
49324928

49334929
static int
4934-
Test___init___impl(TestObj *self, PyObject *args);
4930+
Test___init___impl(TestObj *self, Py_ssize_t nargs, PyObject *const *args);
49354931

49364932
static int
49374933
Test___init__(PyObject *self, PyObject *args, PyObject *kwargs)
49384934
{
49394935
int return_value = -1;
49404936
PyTypeObject *base_tp = TestType;
4941-
PyObject *__clinic_args = NULL;
4937+
PyObject *const *__clinic_args = NULL;
49424938

49434939
if ((Py_IS_TYPE(self, base_tp) ||
49444940
Py_TYPE(self)->tp_new == base_tp->tp_new) &&
@@ -4948,17 +4944,16 @@ Test___init__(PyObject *self, PyObject *args, PyObject *kwargs)
49484944
if (!_PyArg_CheckPositional("Test", PyTuple_GET_SIZE(args), 0, PY_SSIZE_T_MAX)) {
49494945
goto exit;
49504946
}
4951-
__clinic_args = PyTuple_GetSlice(0, -1);
4952-
return_value = Test___init___impl((TestObj *)self, __clinic_args);
4947+
__clinic_args = _PyTuple_CAST(args)->ob_item;
4948+
return_value = Test___init___impl((TestObj *)self, nvararg, __clinic_args);
49534949

49544950
exit:
4955-
Py_XDECREF(__clinic_args);
49564951
return return_value;
49574952
}
49584953

49594954
static int
4960-
Test___init___impl(TestObj *self, PyObject *args)
4961-
/*[clinic end generated code: output=0ed1009fe0dcf98d input=2a8bd0033c9ac772]*/
4955+
Test___init___impl(TestObj *self, Py_ssize_t nargs, PyObject *const *args)
4956+
/*[clinic end generated code: output=6a64b417c9080a73 input=2a8bd0033c9ac772]*/
49624957

49634958

49644959
/*[clinic input]
@@ -4976,14 +4971,14 @@ PyDoc_STRVAR(Test__doc__,
49764971
"Varargs new method. For example, nargs is translated to PyTuple_GET_SIZE.");
49774972

49784973
static PyObject *
4979-
Test_impl(PyTypeObject *type, PyObject *args);
4974+
Test_impl(PyTypeObject *type, Py_ssize_t nargs, PyObject *const *args);
49804975

49814976
static PyObject *
49824977
Test(PyTypeObject *type, PyObject *args, PyObject *kwargs)
49834978
{
49844979
PyObject *return_value = NULL;
49854980
PyTypeObject *base_tp = TestType;
4986-
PyObject *__clinic_args = NULL;
4981+
PyObject *const *__clinic_args = NULL;
49874982

49884983
if ((type == base_tp || type->tp_init == base_tp->tp_init) &&
49894984
!_PyArg_NoKeywords("Test", kwargs)) {
@@ -4992,17 +4987,16 @@ Test(PyTypeObject *type, PyObject *args, PyObject *kwargs)
49924987
if (!_PyArg_CheckPositional("Test", PyTuple_GET_SIZE(args), 0, PY_SSIZE_T_MAX)) {
49934988
goto exit;
49944989
}
4995-
__clinic_args = PyTuple_GetSlice(0, -1);
4996-
return_value = Test_impl(type, __clinic_args);
4990+
__clinic_args = _PyTuple_CAST(args)->ob_item;
4991+
return_value = Test_impl(type, nvararg, __clinic_args);
49974992

49984993
exit:
4999-
Py_XDECREF(__clinic_args);
50004994
return return_value;
50014995
}
50024996

50034997
static PyObject *
5004-
Test_impl(PyTypeObject *type, PyObject *args)
5005-
/*[clinic end generated code: output=8b219f6633e2a2e9 input=70ad829df3dd9b84]*/
4998+
Test_impl(PyTypeObject *type, Py_ssize_t nargs, PyObject *const *args)
4999+
/*[clinic end generated code: output=bf22f942407383a5 input=70ad829df3dd9b84]*/
50065000

50075001

50085002
/*[clinic input]

Lib/test/test_clinic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3381,8 +3381,8 @@ def test_keyword_only_parameter(self):
33813381
def test_varpos(self):
33823382
# fn(*args)
33833383
fn = ac_tester.varpos
3384-
self.assertEqual(fn(), ())
3385-
self.assertEqual(fn(1, 2), (1, 2))
3384+
self.assertEqual(fn(), ((),))
3385+
self.assertEqual(fn(1, 2), ((1, 2),))
33863386

33873387
def test_posonly_varpos(self):
33883388
# fn(a, b, /, *args)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Avoid temporary tuple creation for vararg in argument passing with Argument
2+
Clinic generated code (if arguments either vararg or positional-only).

Modules/_testclinic.c

+35-8
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ pack_arguments_newref(int argc, ...)
5858
return tuple;
5959
}
6060

61+
static PyObject *
62+
pack_varargs_to_tuple(Py_ssize_t varargssize, PyObject *const *args)
63+
{
64+
assert(!PyErr_Occurred());
65+
PyObject *tuple = PyTuple_New(varargssize);
66+
if (!tuple) {
67+
return NULL;
68+
}
69+
for (Py_ssize_t i = 0; i < varargssize; i++) {
70+
PyTuple_SET_ITEM(tuple, i, Py_NewRef(args[i]));
71+
}
72+
return tuple;
73+
}
74+
6175
/* Pack arguments to a tuple.
6276
* `wrapper` is function which converts primitive type to PyObject.
6377
* `arg_type` is type that arguments should be converted to before wrapped. */
@@ -970,10 +984,16 @@ varpos
970984
[clinic start generated code]*/
971985

972986
static PyObject *
973-
varpos_impl(PyObject *module, PyObject *args)
974-
/*[clinic end generated code: output=7b0b9545872bdca4 input=f87cd674145d394c]*/
987+
varpos_impl(PyObject *module, Py_ssize_t nargs, PyObject *const *args)
988+
/*[clinic end generated code: output=b65096f423fb5dcc input=f87cd674145d394c]*/
975989
{
976-
return Py_NewRef(args);
990+
PyObject *vararg_tuple = pack_varargs_to_tuple(nargs, args);
991+
if (!vararg_tuple) {
992+
return NULL;
993+
}
994+
PyObject *result = pack_arguments_newref(1, vararg_tuple);
995+
Py_DECREF(vararg_tuple);
996+
return result;
977997
}
978998

979999

@@ -989,10 +1009,16 @@ posonly_varpos
9891009

9901010
static PyObject *
9911011
posonly_varpos_impl(PyObject *module, PyObject *a, PyObject *b,
992-
PyObject *args)
993-
/*[clinic end generated code: output=5dae5eb2a0d623cd input=c9fd7895cfbaabba]*/
1012+
Py_ssize_t nargs, PyObject *const *args)
1013+
/*[clinic end generated code: output=d10d43d86d117ab3 input=c9fd7895cfbaabba]*/
9941014
{
995-
return pack_arguments_newref(3, a, b, args);
1015+
PyObject *vararg_tuple = pack_varargs_to_tuple(nargs, args);
1016+
if (!vararg_tuple) {
1017+
return NULL;
1018+
}
1019+
PyObject *result = pack_arguments_newref(3, a, b, vararg_tuple);
1020+
Py_DECREF(vararg_tuple);
1021+
return result;
9961022
}
9971023

9981024

@@ -1157,8 +1183,9 @@ Proof-of-concept of GH-99233 refcount error bug.
11571183
[clinic start generated code]*/
11581184

11591185
static PyObject *
1160-
gh_99233_refcount_impl(PyObject *module, PyObject *args)
1161-
/*[clinic end generated code: output=585855abfbca9a7f input=eecfdc2092d90dc3]*/
1186+
gh_99233_refcount_impl(PyObject *module, Py_ssize_t nargs,
1187+
PyObject *const *args)
1188+
/*[clinic end generated code: output=b570007e61e5c670 input=eecfdc2092d90dc3]*/
11621189
{
11631190
Py_RETURN_NONE;
11641191
}

Modules/clinic/_testclinic.c.h

+17-34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/clinic/gcmodule.c.h

+13-23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)