This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Add METH_FASTCALL: new calling convention for C functions
Type: enhancement Stage:
Components: Versions: Python 3.6
process
Status: closed Resolution: fixed
Dependencies: 27830 Superseder:
Assigned To: Nosy List: jdemeyer, python-dev, scoder, serhiy.storchaka, vstinner
Priority: normal Keywords: patch

Created on 2016-08-20 00:32 by vstinner, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
fastcall.patch vstinner, 2016-09-10 00:56 review
Messages (18)
msg273173 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-08-20 00:32
The issue #27128 added _PyObject_FastCall() to avoid the creation of temporary tuples when calling functions.

I propose to add a new METH_FASTCALL calling convention. The example using METH_VARARGS | METH_KEYWORDS:
   PyObject* func(DirEntry *self, PyObject *args, PyObject *kwargs)
becomes:
   PyObject* func(DirEntry *self, PyObject **args, int nargs, PyObject *kwargs)

Using METH_VARARGS, args is a Python tuple. Using METH_FASTCALL, args is a C array of PyObject*, and there is a second nargs parameter.

Later, Argument Clinic will be modified to *generate* code using the new METH_FASTCALL calling convention. Code written with Argument Clinic will only need to be updated by Argument Clinic to get the new faster calling convention (avoid the creation of a temporary tuple for positional arguments).

This issue depends on the issue #27809 "_PyObject_FastCall(): add support for keyword arguments". I will wait until this dependency is implemented, before working on the implementation of this part.

For a full implementation, see my first attempt in the issue #26814. I will extract the code from this branch to write a new patch.
msg273336 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2016-08-22 05:58
I agree that this would be cool. There is a tiny bit of a backwards compatibility concern as the new function signature would be incompatible with anything we had before, but I would guess that any code that chooses to bypass PyObject_Call() & friends would at least fall back to using it if it finds flags that it cannot handle. Cython code definitely does and always did, but there might be bugs. This change is the perfect way to "find" those. ;-)

Anyway, Victor, I definitely appreciate your efforts in this direction.
msg273339 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-08-22 08:06
> There is a tiny bit of a backwards compatibility concern as the new function signature would be incompatible with anything we had before,

Right. If you call directly PyCFunction functions, you will likely get quickly a crash. But... who call directly PyCFunction functions? Why not using the 30+ functions to call functions?

Hopefully, it's easy to support METH_FASTCALL in an existing function getting a tuple:

   int nargs = (int)PyTuple_GET_SIZE(args);
   PyObject **stack = &PyTuple_GETITEM(args, 0);
   result = func(self, stack, nargs, kwargs);

I guess that Cython calls directly PyCFunction. cpyext module of PyPy probably too. Ok, except of them, are you aware of other projects doing that?
msg273340 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2016-08-22 08:44
Extensive callback interfaces like map() come to mind, where a large number of calls becomes excessively time critical and might thus have made people implement their own special purpose calling code.

However, I don't know any such code (outside of Cython) and I agree that this is such a special optimisation that there can't be many cases. If there are others, they'll likely be using the obvious fallback already, or otherwise just adapt when Py3.6 comes out and move on. The fix isn't complex at all.

I do not consider this a road block.
msg273341 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-08-22 09:05
> Extensive callback interfaces like map() come to mind, where a large number of calls becomes excessively time critical and might thus have made people implement their own special purpose calling code.

I didn't patch map_next() of Python/bltinmodule.c nor list.sort() for use fast call yet. With my full patch, map() and list.sort() are *faster*, but even more changes are needed ;-)

https://bugs.python.org/issue26814#msg263999

- map(lambda x: x, list(range(1000))): 18% faster
- sorted(list, key=lambda x: x): 33% faster

I'm now aware of map() or list.sort()/sorted() function rewritten from scratch for better performance, but I can imagine that someone did that. But do these specialized implementation use PyObject_Call() or "inline" PyCFunction_Call()?

If someone "inlines" PyCFunction_Call(), be prepared to "backward incompatible changes", since it's common that CPython internals change in a subtle way.


> However, I don't know any such code (outside of Cython)

I used GitHub to search for such code using "case METH_VARARGS". I found:

* (many copies of the CPython source code)
* https://github.com/jhgameboy/ironclad/blob/5892c43b540b216d638e0fed2e6cf3fd8289fdfc/src/CallableBuilder.cs : ironclad is a module to call C extensions from IronPython, the last commit of the project was in 2011...
* https://github.com/pepsipepsi/nodebox_opengl_python3/blob/cfb2633df1055a028672b11311603cc2241a1378/nodebox/ext/psyco/src/c/Objects/pmethodobject.c : copy of the psyco module. The last commit in pysco was also in 2011, https://bitbucket.org/arigo/psyco/commits/all

In short, I found nothing. But I didn't search far, maybe there are a few other code bases using "case METH_VARARGS:"?
msg273409 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-08-22 22:46
According to discussions in the issue #27809, require a Python dict leads to inefficient code because a temporary dict may be required. The issue #27830 proposes to pass keyword arguments as (key, value) pairs.

It should be used for the new METH_FASTCALL calling convention.
msg273679 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-08-25 21:35
I changed the dependency to the issue #27830 "Add _PyObject_FastCallKeywords(): avoid the creation of a temporary dictionary for keyword arguments" which itself depends on the issue #27213 "Rework CALL_FUNCTION* opcodes".
msg275447 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-09-09 21:23
New changeset a25c39873d93 by Victor Stinner in branch 'default':
Issue #27810: Add _PyCFunction_FastCallKeywords()
https://hg.python.org/cpython/rev/a25c39873d93
msg275516 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-09-10 00:56
fastcall.patch combines two changes:

changeset:   103513:74abb8ddf7f2
tag:         tip
user:        Victor Stinner <victor.stinner@gmail.com>
date:        Fri Sep 09 17:40:38 2016 -0700
files:       Include/modsupport.h Python/getargs.c Tools/clinic/clinic.py
description:
Emit METH_FASTCALL code in Argument Clinic

Issue #27810:

* Modify vgetargskeywordsfast() to work on a C array of PyObject* rather than
  working on a tuple directly.
* Add _PyArg_ParseStack()
* Argument Clinic now emits code using the new METH_FASTCALL calling convention


changeset:   103512:d55abcddd194
user:        Victor Stinner <victor.stinner@gmail.com>
date:        Fri Sep 09 17:40:22 2016 -0700
files:       Include/abstract.h Include/methodobject.h Objects/abstract.c Objects/methodobject.c
description:
Add METH_FASTCALL calling convention

Issue #27810: Add a new calling convention for C functions:

    PyObject* func(PyObject *self, PyObject **args,
                   Py_ssize_t nargs, PyObject *kwnames);

Where args is a C array of positional arguments followed by values of keyword
arguments. nargs is the number of positional arguments, kwnames are keys of
keyword arguments. kwnames can be NULL.
msg275555 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-09-10 03:22
New changeset 86b0f5a900c7 by Victor Stinner in branch 'default':
Add METH_FASTCALL calling convention
https://hg.python.org/cpython/rev/86b0f5a900c7

New changeset 633f850038c3 by Victor Stinner in branch 'default':
Emit METH_FASTCALL code in Argument Clinic
https://hg.python.org/cpython/rev/633f850038c3

New changeset 97a68adbe826 by Victor Stinner in branch 'default':
Issue #27810: Rerun Argument Clinic on all modules
https://hg.python.org/cpython/rev/97a68adbe826
msg275560 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-09-10 03:57
New changeset 3934e070c9db by Victor Stinner in branch 'default':
Issue #27810: Fix getargs.c compilation on Windows
https://hg.python.org/cpython/rev/3934e070c9db
msg275826 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-09-11 18:28
New changeset 603bef7bb744 by Serhiy Storchaka in branch 'default':
Issue #27810: Regenerate Argument Clinic.
https://hg.python.org/cpython/rev/603bef7bb744
msg276033 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-09-12 12:59
Stefan Behnel: "There is a tiny bit of a backwards compatibility concern as the new function signature would be incompatible with anything we had before,"

Python 3.6 will probably have two "fast call" calling convention:

* _PyObject_FastCallDict(): expect a Python dict for keyword arguments
* _PyObject_FastCallKeywods(): expect a Python tuple for keys of keyword arguments, keyword values are packed in the same array than positional arguments

_PyObject_FastCallKeywods() is not really written to be called directly: Python/ceval.c calls you, but you may call _PyObject_FastCallKeywods() again "wrapper" functions, like functools.partial().

Currently, tp_call (and tp_init and tp_new) still expects a (tuple, dict) for positional and keyword arguments, but later I will add something to also support METH_FASTCALL for callable objects. I just don't know yet what is the best option to make this change.

--

The main idea is implemented (implement METH_FASTCALL), I close the issue.

I will open new issues for more specific changes, and maybe extend the API (especially tp_call).
msg276046 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-09-12 13:52
Could you please wrap "#define METH_FASTCALL  0x0080" with "#ifndef Py_LIMITED_API"/"#endif"? I think this method still is not stable, and we shouldn't encourage using it in third-party extensions.

I would use two methods: just METH_FASTCALL and METH_FASTCALL|METH_KEYWORDS. The former is simpler and more stable, it takes just an array of positional parameters. The latter takes also keyword arguments, and the format of passing keyword arguments may be changed.
msg276047 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-09-12 13:56
New changeset 08a500e8b482 by Victor Stinner in branch 'default':
Issue #27810: Exclude METH_FASTCALL from the stable API
https://hg.python.org/cpython/rev/08a500e8b482
msg276048 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2016-09-12 13:56
> Could you please wrap "#define METH_FASTCALL  0x0080" with "#ifndef Py_LIMITED_API"/"#endif"?

Sorry, this is a mistake. I tried to exclude all new symbols related to fast call from the stable API.

It should now be fixed.
msg283332 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-12-15 16:02
New changeset ecd218c41cd4 by Victor Stinner in branch 'default':
Use _PyDict_NewPresized() in _PyStack_AsDict()
https://hg.python.org/cpython/rev/ecd218c41cd4
msg342518 - (view) Author: Jeroen Demeyer (jdemeyer) * (Python triager) Date: 2019-05-14 20:40
Breakage due to the usage of borrowed references in _PyStack_UnpackDict(): #36907
History
Date User Action Args
2022-04-11 14:58:35adminsetgithub: 71997
2019-05-14 20:40:00jdemeyersetnosy: + jdemeyer
messages: + msg342518
2016-12-15 16:02:28python-devsetmessages: + msg283332
2016-09-12 13:56:36vstinnersetmessages: + msg276048
2016-09-12 13:56:26python-devsetmessages: + msg276047
2016-09-12 13:52:14serhiy.storchakasetmessages: + msg276046
2016-09-12 12:59:03vstinnersetstatus: open -> closed
resolution: fixed
messages: + msg276033
2016-09-11 18:28:53python-devsetmessages: + msg275826
2016-09-10 03:57:26python-devsetmessages: + msg275560
2016-09-10 03:22:42python-devsetmessages: + msg275555
2016-09-10 00:56:45vstinnersetfiles: + fastcall.patch
keywords: + patch
messages: + msg275516
2016-09-09 21:23:13python-devsetnosy: + python-dev
messages: + msg275447
2016-08-25 21:35:07vstinnersetdependencies: + Add _PyObject_FastCallKeywords(): avoid the creation of a temporary dictionary for keyword arguments, - Add _PyFunction_FastCallDict(): fast call with keyword arguments as a dict
messages: + msg273679
2016-08-22 22:46:07vstinnersetmessages: + msg273409
2016-08-22 09:05:24vstinnersetmessages: + msg273341
2016-08-22 08:44:57scodersetmessages: + msg273340
2016-08-22 08:06:46vstinnersetmessages: + msg273339
2016-08-22 05:58:02scodersetmessages: + msg273336
2016-08-21 11:42:02scodersetnosy: + scoder
2016-08-20 00:32:56vstinnersetnosy: + serhiy.storchaka
2016-08-20 00:32:48vstinnersetdependencies: + Add _PyFunction_FastCallDict(): fast call with keyword arguments as a dict
2016-08-20 00:32:46vstinnercreate