Skip to content

Commit d873572

Browse files
committed
Add _PyObject_FastCallKeywords()
Issue python#27830: Add _PyObject_FastCallKeywords(): avoid the creation of a temporary dictionary for keyword arguments. Other changes: * Cleanup call_function() and fast_function() (ex: rename nk to nkwargs) * Remove now useless do_call(), replaced with _PyObject_FastCallKeywords()
1 parent 84f6a8f commit d873572

File tree

4 files changed

+130
-55
lines changed

4 files changed

+130
-55
lines changed

Include/abstract.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
271271
Py_ssize_t nargs);
272272

273273
/* Call the callable object func with the "fast call" calling convention:
274-
args is a C array for positional parameters (nargs is the number of
275-
positional paramater), kwargs is a dictionary for keyword parameters.
274+
args is a C array for positional arguments (nargs is the number of
275+
positional arguments), kwargs is a dictionary for keyword arguments.
276276
277277
If nargs is equal to zero, args can be NULL. kwargs can be NULL.
278278
nargs must be greater or equal to zero.
@@ -283,6 +283,24 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
283283
PyObject **args, Py_ssize_t nargs,
284284
PyObject *kwargs);
285285

286+
/* Call the callable object func with the "fast call" calling convention:
287+
args is a C array for positional arguments followed by values of
288+
keyword arguments. Keys of keyword arguments are stored as a tuple
289+
of strings in kwnames. nargs is the number of positional parameters at
290+
the beginning of stack. The size of kwnames gives the number of keyword
291+
values in the stack after positional arguments.
292+
293+
If nargs is equal to zero and there is no keyword argument (kwnames is
294+
NULL or its size is zero), args can be NULL.
295+
296+
Return the result on success. Raise an exception and return NULL on
297+
error. */
298+
PyAPI_FUNC(PyObject *) _PyObject_FastCallKeywords
299+
(PyObject *func,
300+
PyObject **args,
301+
Py_ssize_t nargs,
302+
PyObject *kwnames);
303+
286304
#define _PyObject_FastCall(func, args, nargs) \
287305
_PyObject_FastCallDict((func), (args), (nargs), NULL)
288306

Include/funcobject.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ PyAPI_FUNC(PyObject *) _PyFunction_FastCallDict(
6464
PyObject **args,
6565
Py_ssize_t nargs,
6666
PyObject *kwargs);
67+
68+
PyAPI_FUNC(PyObject *) _PyFunction_FastCallKeywords(
69+
PyObject *func,
70+
PyObject **stack,
71+
Py_ssize_t nargs,
72+
PyObject *kwnames);
6773
#endif
6874

6975
/* Macros for direct access to these values. Type checks are *not*

Objects/abstract.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2366,6 +2366,74 @@ _PyObject_Call_Prepend(PyObject *func,
23662366
return result;
23672367
}
23682368

2369+
static PyObject *
2370+
_PyStack_AsDict(PyObject **values, Py_ssize_t nkwargs, PyObject *kwnames,
2371+
PyObject *func)
2372+
{
2373+
PyObject *kwdict;
2374+
Py_ssize_t i;
2375+
2376+
kwdict = PyDict_New();
2377+
if (kwdict == NULL) {
2378+
return NULL;
2379+
}
2380+
2381+
for (i=0; i < nkwargs; i++) {
2382+
int err;
2383+
PyObject *key = PyTuple_GET_ITEM(kwnames, i);
2384+
PyObject *value = *values++;
2385+
2386+
if (PyDict_GetItem(kwdict, key) != NULL) {
2387+
PyErr_Format(PyExc_TypeError,
2388+
"%.200s%s got multiple values "
2389+
"for keyword argument '%U'",
2390+
PyEval_GetFuncName(func),
2391+
PyEval_GetFuncDesc(func),
2392+
key);
2393+
Py_DECREF(kwdict);
2394+
return NULL;
2395+
}
2396+
2397+
err = PyDict_SetItem(kwdict, key, value);
2398+
if (err) {
2399+
Py_DECREF(kwdict);
2400+
return NULL;
2401+
}
2402+
}
2403+
return kwdict;
2404+
}
2405+
2406+
PyObject *
2407+
_PyObject_FastCallKeywords(PyObject *func, PyObject **stack, Py_ssize_t nargs,
2408+
PyObject *kwnames)
2409+
{
2410+
PyObject *kwdict, *result;
2411+
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
2412+
2413+
assert(nargs >= 0);
2414+
assert(kwnames == NULL || PyTuple_CheckExact(kwnames));
2415+
assert((nargs == 0 && nkwargs == 0) || stack != NULL);
2416+
2417+
if (PyFunction_Check(func)) {
2418+
/* Fast-path: avoid temporary tuple or dict */
2419+
return _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
2420+
}
2421+
2422+
if (nkwargs > 0) {
2423+
kwdict = _PyStack_AsDict(stack + nargs, nkwargs, kwnames, func);
2424+
if (kwdict == NULL) {
2425+
return NULL;
2426+
}
2427+
}
2428+
else {
2429+
kwdict = NULL;
2430+
}
2431+
2432+
result = _PyObject_FastCallDict(func, stack, nargs, kwdict);
2433+
Py_XDECREF(kwdict);
2434+
return result;
2435+
}
2436+
23692437
static PyObject*
23702438
call_function_tail(PyObject *callable, PyObject *args)
23712439
{

Python/ceval.c

Lines changed: 36 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ static PyObject * call_function(PyObject ***, Py_ssize_t, PyObject *, uint64*, u
113113
#else
114114
static PyObject * call_function(PyObject ***, Py_ssize_t, PyObject *);
115115
#endif
116-
static PyObject * fast_function(PyObject *, PyObject ***, Py_ssize_t, PyObject *);
117-
static PyObject * do_call(PyObject *, PyObject ***, Py_ssize_t, PyObject *);
116+
static PyObject * fast_function(PyObject *, PyObject **, Py_ssize_t, PyObject *);
118117
static PyObject * do_call_core(PyObject *, PyObject *, PyObject *);
119118
static PyObject * create_keyword_args(PyObject *, PyObject ***, PyObject *);
120119
static PyObject * load_args(PyObject ***, Py_ssize_t);
@@ -4940,7 +4939,7 @@ if (tstate->use_tracing && tstate->c_profilefunc) { \
49404939
}
49414940

49424941
static PyObject *
4943-
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
4942+
call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames
49444943
#ifdef WITH_TSC
49454944
, uint64* pintr0, uint64* pintr1
49464945
#endif
@@ -4949,8 +4948,8 @@ call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
49494948
PyObject **pfunc = (*pp_stack) - oparg - 1;
49504949
PyObject *func = *pfunc;
49514950
PyObject *x, *w;
4952-
Py_ssize_t nk = names == NULL ? 0 : PyTuple_GET_SIZE(names);
4953-
Py_ssize_t nargs = oparg - nk;
4951+
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
4952+
Py_ssize_t nargs = oparg - nkwargs;
49544953

49554954
/* Always dispatch PyCFunction first, because these are
49564955
presumed to be the most frequent callable object.
@@ -4960,7 +4959,7 @@ call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
49604959
PyThreadState *tstate = PyThreadState_GET();
49614960

49624961
PCALL(PCALL_CFUNCTION);
4963-
if (names == NULL && flags & (METH_NOARGS | METH_O)) {
4962+
if (kwnames == NULL && flags & (METH_NOARGS | METH_O)) {
49644963
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
49654964
PyObject *self = PyCFunction_GET_SELF(func);
49664965
if (flags & METH_NOARGS && nargs == 0) {
@@ -4982,8 +4981,8 @@ call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
49824981
}
49834982
else {
49844983
PyObject *callargs, *kwdict = NULL;
4985-
if (names != NULL) {
4986-
kwdict = create_keyword_args(names, pp_stack, func);
4984+
if (kwnames != NULL) {
4985+
kwdict = create_keyword_args(kwnames, pp_stack, func);
49874986
if (kwdict == NULL) {
49884987
x = NULL;
49894988
goto cfuncerror;
@@ -5003,6 +5002,9 @@ call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
50035002
}
50045003
}
50055004
else {
5005+
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
5006+
PyObject **stack;
5007+
50065008
if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {
50075009
/* optimize access to bound methods */
50085010
PyObject *self = PyMethod_GET_SELF(func);
@@ -5018,11 +5020,14 @@ call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
50185020
Py_INCREF(func);
50195021
}
50205022

5023+
stack = (*pp_stack) - nargs - nkwargs;
5024+
50215025
READ_TIMESTAMP(*pintr0);
50225026
if (PyFunction_Check(func)) {
5023-
x = fast_function(func, pp_stack, nargs, names);
5024-
} else {
5025-
x = do_call(func, pp_stack, nargs, names);
5027+
x = fast_function(func, stack, nargs, kwnames);
5028+
}
5029+
else {
5030+
x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames);
50265031
}
50275032
READ_TIMESTAMP(*pintr1);
50285033

@@ -5055,8 +5060,8 @@ call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *names
50555060
*/
50565061

50575062
static PyObject*
5058-
_PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t nargs,
5059-
PyObject *globals)
5063+
_PyFunction_FastCall(PyCodeObject *co, PyObject **args, Py_ssize_t nargs,
5064+
PyObject *globals)
50605065
{
50615066
PyFrameObject *f;
50625067
PyThreadState *tstate = PyThreadState_GET();
@@ -5091,19 +5096,19 @@ _PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t nargs,
50915096
return result;
50925097
}
50935098

5094-
/* Similar to _PyFunction_FastCall() but keywords are passed a (key, value)
5095-
pairs in stack */
50965099
static PyObject *
5097-
fast_function(PyObject *func, PyObject ***pp_stack, Py_ssize_t nargs, PyObject *names)
5100+
fast_function(PyObject *func, PyObject **stack,
5101+
Py_ssize_t nargs, PyObject *kwnames)
50985102
{
50995103
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
51005104
PyObject *globals = PyFunction_GET_GLOBALS(func);
51015105
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
51025106
PyObject *kwdefs, *closure, *name, *qualname;
51035107
PyObject **d;
5104-
Py_ssize_t nkwargs = names == NULL ? 0 : PyTuple_GET_SIZE(names);
5108+
Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);
51055109
Py_ssize_t nd;
5106-
PyObject **stack = (*pp_stack)-nargs-nkwargs;
5110+
5111+
assert((nargs == 0 && nkwargs == 0) || stack != NULL);
51075112

51085113
PCALL(PCALL_FUNCTION);
51095114
PCALL(PCALL_FAST_FUNCTION);
@@ -5112,15 +5117,14 @@ fast_function(PyObject *func, PyObject ***pp_stack, Py_ssize_t nargs, PyObject *
51125117
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE))
51135118
{
51145119
if (argdefs == NULL && co->co_argcount == nargs) {
5115-
return _PyFunction_FastCallNoKw(co, stack, nargs, globals);
5120+
return _PyFunction_FastCall(co, stack, nargs, globals);
51165121
}
51175122
else if (nargs == 0 && argdefs != NULL
51185123
&& co->co_argcount == Py_SIZE(argdefs)) {
51195124
/* function called with no arguments, but all parameters have
51205125
a default value: use default values as arguments .*/
51215126
stack = &PyTuple_GET_ITEM(argdefs, 0);
5122-
return _PyFunction_FastCallNoKw(co, stack, Py_SIZE(argdefs),
5123-
globals);
5127+
return _PyFunction_FastCall(co, stack, Py_SIZE(argdefs), globals);
51245128
}
51255129
}
51265130

@@ -5140,11 +5144,18 @@ fast_function(PyObject *func, PyObject ***pp_stack, Py_ssize_t nargs, PyObject *
51405144
return _PyEval_EvalCodeWithName((PyObject*)co, globals, (PyObject *)NULL,
51415145
stack, nargs,
51425146
NULL, 0,
5143-
names, stack + nargs,
5147+
kwnames, stack + nargs,
51445148
d, (int)nd, kwdefs,
51455149
closure, name, qualname);
51465150
}
51475151

5152+
PyObject *
5153+
_PyFunction_FastCallKeywords(PyObject *func, PyObject **stack,
5154+
Py_ssize_t nargs, PyObject *kwnames)
5155+
{
5156+
return fast_function(func, stack, nargs, kwnames);
5157+
}
5158+
51485159
PyObject *
51495160
_PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
51505161
PyObject *kwargs)
@@ -5172,15 +5183,14 @@ _PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
51725183
{
51735184
/* Fast paths */
51745185
if (argdefs == NULL && co->co_argcount == nargs) {
5175-
return _PyFunction_FastCallNoKw(co, args, nargs, globals);
5186+
return _PyFunction_FastCall(co, args, nargs, globals);
51765187
}
51775188
else if (nargs == 0 && argdefs != NULL
51785189
&& co->co_argcount == Py_SIZE(argdefs)) {
51795190
/* function called with no arguments, but all parameters have
51805191
a default value: use default values as arguments .*/
51815192
args = &PyTuple_GET_ITEM(argdefs, 0);
5182-
return _PyFunction_FastCallNoKw(co, args, Py_SIZE(argdefs),
5183-
globals);
5193+
return _PyFunction_FastCall(co, args, Py_SIZE(argdefs), globals);
51845194
}
51855195
}
51865196

@@ -5242,8 +5252,8 @@ create_keyword_args(PyObject *names, PyObject ***pp_stack,
52425252
return NULL;
52435253
while (--nk >= 0) {
52445254
int err;
5245-
PyObject *value = EXT_POP(*pp_stack);
52465255
PyObject *key = PyTuple_GET_ITEM(names, nk);
5256+
PyObject *value = EXT_POP(*pp_stack);
52475257
if (PyDict_GetItem(kwdict, key) != NULL) {
52485258
PyErr_Format(PyExc_TypeError,
52495259
"%.200s%s got multiple values "
@@ -5281,33 +5291,6 @@ load_args(PyObject ***pp_stack, Py_ssize_t nargs)
52815291
return args;
52825292
}
52835293

5284-
static PyObject *
5285-
do_call(PyObject *func, PyObject ***pp_stack, Py_ssize_t nargs, PyObject *kwnames)
5286-
{
5287-
PyObject *callargs, *kwdict, *result;
5288-
5289-
if (kwnames != NULL) {
5290-
kwdict = create_keyword_args(kwnames, pp_stack, func);
5291-
if (kwdict == NULL) {
5292-
return NULL;
5293-
}
5294-
}
5295-
else {
5296-
kwdict = NULL;
5297-
}
5298-
5299-
callargs = load_args(pp_stack, nargs);
5300-
if (callargs == NULL) {
5301-
Py_XDECREF(kwdict);
5302-
return NULL;
5303-
}
5304-
5305-
result = do_call_core(func, callargs, kwdict);
5306-
Py_XDECREF(callargs);
5307-
Py_XDECREF(kwdict);
5308-
return result;
5309-
}
5310-
53115294
static PyObject *
53125295
do_call_core(PyObject *func, PyObject *callargs, PyObject *kwdict)
53135296
{

0 commit comments

Comments
 (0)