Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7fc252a

Browse files
committed
Optimize _PyCFunction_FastCallKeywords()
Issue #29259: Write fast path in _PyCFunction_FastCallKeywords() for METH_FASTCALL, avoid the creation of a temporary dictionary for keyword arguments. Cleanup also _PyCFunction_FastCallDict(): * Don't dereference func before checking that it's not NULL * Move code to raise the "no keyword argument" exception into a new no_keyword_error label. Update python-gdb.py for the change.
1 parent 15f9459 commit 7fc252a

2 files changed

Lines changed: 133 additions & 37 deletions

File tree

Objects/methodobject.c

Lines changed: 131 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
8989

9090
assert(kwds == NULL || PyDict_Check(kwds));
9191
/* PyCFunction_Call() must not be called with an exception set,
92-
because it may clear it (directly or indirectly) and so the
92+
because it can clear it (directly or indirectly) and so the
9393
caller loses its exception */
9494
assert(!PyErr_Occurred());
9595

@@ -155,14 +155,14 @@ PyObject *
155155
_PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
156156
PyObject *kwargs)
157157
{
158-
PyCFunctionObject *func = (PyCFunctionObject*)func_obj;
159-
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
160-
PyObject *self = PyCFunction_GET_SELF(func);
158+
PyCFunctionObject *func;
159+
PyCFunction meth;
160+
PyObject *self;
161161
PyObject *result;
162162
int flags;
163163

164-
assert(PyCFunction_Check(func));
165-
assert(func != NULL);
164+
assert(func_obj != NULL);
165+
assert(PyCFunction_Check(func_obj));
166166
assert(nargs >= 0);
167167
assert(nargs == 0 || args != NULL);
168168
assert(kwargs == NULL || PyDict_Check(kwargs));
@@ -172,55 +172,51 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
172172
caller loses its exception */
173173
assert(!PyErr_Occurred());
174174

175+
func = (PyCFunctionObject*)func_obj;
176+
meth = PyCFunction_GET_FUNCTION(func);
177+
self = PyCFunction_GET_SELF(func);
175178
flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
176179

177180
switch (flags)
178181
{
179182
case METH_NOARGS:
183+
if (nargs != 0) {
184+
goto no_keyword_error;
185+
}
186+
180187
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
181188
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
182189
func->m_ml->ml_name);
183190
return NULL;
184191
}
185192

186-
if (nargs != 0) {
187-
PyErr_Format(PyExc_TypeError,
188-
"%.200s() takes no arguments (%zd given)",
189-
func->m_ml->ml_name, nargs);
190-
return NULL;
191-
}
192-
193193
result = (*meth) (self, NULL);
194194
break;
195195

196196
case METH_O:
197-
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
198-
PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
199-
func->m_ml->ml_name);
200-
return NULL;
201-
}
202-
203197
if (nargs != 1) {
204198
PyErr_Format(PyExc_TypeError,
205199
"%.200s() takes exactly one argument (%zd given)",
206200
func->m_ml->ml_name, nargs);
207201
return NULL;
208202
}
209203

204+
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
205+
goto no_keyword_error;
206+
}
207+
210208
result = (*meth) (self, args[0]);
211209
break;
212210

213211
case METH_VARARGS:
214212
case METH_VARARGS | METH_KEYWORDS:
215213
{
216-
/* Slow-path: create a temporary tuple */
214+
/* Slow-path: create a temporary tuple for positional arguments */
217215
PyObject *tuple;
218216

219-
if (!(flags & METH_KEYWORDS) && kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
220-
PyErr_Format(PyExc_TypeError,
221-
"%.200s() takes no keyword arguments",
222-
func->m_ml->ml_name);
223-
return NULL;
217+
if (!(flags & METH_KEYWORDS)
218+
&& kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
219+
goto no_keyword_error;
224220
}
225221

226222
tuple = _PyStack_AsTuple(args, nargs);
@@ -267,35 +263,134 @@ _PyCFunction_FastCallDict(PyObject *func_obj, PyObject **args, Py_ssize_t nargs,
267263
result = _Py_CheckFunctionResult(func_obj, result, NULL);
268264

269265
return result;
266+
267+
no_keyword_error:
268+
PyErr_Format(PyExc_TypeError,
269+
"%.200s() takes no arguments (%zd given)",
270+
func->m_ml->ml_name, nargs);
271+
return NULL;
270272
}
271273

272274
PyObject *
273-
_PyCFunction_FastCallKeywords(PyObject *func, PyObject **stack,
275+
_PyCFunction_FastCallKeywords(PyObject *func_obj, PyObject **args,
274276
Py_ssize_t nargs, PyObject *kwnames)
275277
{
276-
PyObject *kwdict, *result;
278+
PyCFunctionObject *func;
279+
PyCFunction meth;
280+
PyObject *self, *result;
277281
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
282+
int flags;
278283

279-
assert(PyCFunction_Check(func));
284+
assert(func_obj != NULL);
285+
assert(PyCFunction_Check(func_obj));
280286
assert(nargs >= 0);
281287
assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
282-
assert((nargs == 0 && nkwargs == 0) || stack != NULL);
288+
assert((nargs == 0 && nkwargs == 0) || args != NULL);
283289
/* kwnames must only contains str strings, no subclass, and all keys must
284290
be unique */
285291

286-
if (nkwargs > 0) {
287-
kwdict = _PyStack_AsDict(stack + nargs, kwnames);
288-
if (kwdict == NULL) {
292+
/* _PyCFunction_FastCallKeywords() must not be called with an exception
293+
set, because it can clear it (directly or indirectly) and so the caller
294+
loses its exception */
295+
assert(!PyErr_Occurred());
296+
297+
func = (PyCFunctionObject*)func_obj;
298+
meth = PyCFunction_GET_FUNCTION(func);
299+
self = PyCFunction_GET_SELF(func);
300+
flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);
301+
302+
switch (flags)
303+
{
304+
case METH_NOARGS:
305+
if (nargs != 0) {
306+
PyErr_Format(PyExc_TypeError,
307+
"%.200s() takes no arguments (%zd given)",
308+
func->m_ml->ml_name, nargs);
309+
return NULL;
310+
}
311+
312+
if (nkwargs) {
313+
goto no_keyword_error;
314+
}
315+
316+
result = (*meth) (self, NULL);
317+
break;
318+
319+
case METH_O:
320+
if (nargs != 1) {
321+
PyErr_Format(PyExc_TypeError,
322+
"%.200s() takes exactly one argument (%zd given)",
323+
func->m_ml->ml_name, nargs);
289324
return NULL;
290325
}
326+
327+
if (nkwargs) {
328+
goto no_keyword_error;
329+
}
330+
331+
result = (*meth) (self, args[0]);
332+
break;
333+
334+
case METH_FASTCALL:
335+
/* Fast-path: avoid temporary dict to pass keyword arguments */
336+
result = ((_PyCFunctionFast)meth) (self, args, nargs, kwnames);
337+
break;
338+
339+
case METH_VARARGS:
340+
case METH_VARARGS | METH_KEYWORDS:
341+
{
342+
/* Slow-path: create a temporary tuple for positional arguments
343+
and a temporary dict for keyword arguments */
344+
PyObject *argtuple;
345+
346+
if (!(flags & METH_KEYWORDS) && nkwargs) {
347+
goto no_keyword_error;
348+
}
349+
350+
argtuple = _PyStack_AsTuple(args, nargs);
351+
if (argtuple == NULL) {
352+
return NULL;
353+
}
354+
355+
if (flags & METH_KEYWORDS) {
356+
PyObject *kwdict;
357+
358+
if (nkwargs > 0) {
359+
kwdict = _PyStack_AsDict(args + nargs, kwnames);
360+
if (kwdict == NULL) {
361+
Py_DECREF(argtuple);
362+
return NULL;
363+
}
364+
}
365+
else {
366+
kwdict = NULL;
367+
}
368+
369+
result = (*(PyCFunctionWithKeywords)meth) (self, argtuple, kwdict);
370+
Py_XDECREF(kwdict);
371+
}
372+
else {
373+
result = (*meth) (self, argtuple);
374+
}
375+
Py_DECREF(argtuple);
376+
break;
291377
}
292-
else {
293-
kwdict = NULL;
378+
379+
default:
380+
PyErr_SetString(PyExc_SystemError,
381+
"Bad call flags in _PyCFunction_FastCallKeywords. "
382+
"METH_OLDARGS is no longer supported!");
383+
return NULL;
294384
}
295385

296-
result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict);
297-
Py_XDECREF(kwdict);
386+
result = _Py_CheckFunctionResult(func_obj, result, NULL);
298387
return result;
388+
389+
no_keyword_error:
390+
PyErr_Format(PyExc_TypeError,
391+
"%.200s() takes no keyword arguments",
392+
func->m_ml->ml_name);
393+
return NULL;
299394
}
300395

301396
/* Methods (the standard built-in methods, that is) */

Tools/gdb/libpython.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1518,7 +1518,8 @@ def is_other_python_frame(self):
15181518
except RuntimeError:
15191519
return 'PyCFunction invocation (unable to read "func")'
15201520

1521-
elif caller == '_PyCFunction_FastCallDict':
1521+
elif caller in {'_PyCFunction_FastCallDict',
1522+
'_PyCFunction_FastCallKeywords'}:
15221523
try:
15231524
func = older._gdbframe.read_var('func_obj')
15241525
return str(func)

0 commit comments

Comments
 (0)