You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since the issue bpo-26814 proved that avoiding the creation of temporary tuples to call Python and C functions makes Python faster (between 2% and 29% depending on the benchmark), I extracted a first "minimal" patch to start merging this work.
The first patch adds new functions:
PyObject_CallNoArg(func) and PyObject_CallArg1(func, arg): public functions
_PyObject_FastCall(func, args, nargs, kwargs): private function
I hesitate between the C types "int" and "Py_ssize_t" for nargs. I read once that using "int" can cause performance issues on a loop using "i++" and "data[i]" because the compiler has to handle integer overflow of the int type.
The "int" type is also annoying on Windows 64-bit, it causes compiler warnings on downcast like PyTuple_GET_SIZE(co->co_argcount) stored into a C int.
_PyObject_FastCall() avoids the creation of tuple for:
All Python functions (PyFunction_Check)
C functions using METH_NOARGS or METH_O
The patch removes the "cache tuple" optimization from property_descr_get(), it uses PyObject_CallArg1() instead. It means that the optimization is (currently) missed in some cases compared to the current code, but the code is safer and simpler.
The patch adds Python/pystack.c which currently only contains _PyStack_AsTuple(), but will contain more code later.
I tried to write the smallest patch, but I started to use PyObject_CallNoArg() and PyObject_CallArg1() when the code already created a tuple at each call: PyObject_CallObject(), call_function_tail() and PyEval_CallObjectWithKeywords().
In the patch, keywords are not used in fast calls. But they will be used later. I prefer to start directly with keywords than changing the calling convention once again later.
--
Later, I will propose other patches to:
add METH_FASTCALL calling convention for C functions
modify Argument Clinic to use METH_FASTCALL
So the fast call will be taken in more cases.
--
The long term plan is to slowly use the new FASTCALL calling convention "everywhere". The tricky point are tp_new, tp_init and tp_call attributes of type objects. In the issue bpo-26814, I wrote a patch adding Py_TPFLAGS_FASTNEW, Py_TPFLAGS_FASTINIT and Py_TPFLAGS_FASTCALL flags to use the FASTCALL calling convention for tp_new, tp_init and tp_call. The problem is that calling directly these methods looks common. If we can the calling convention of these methods, it will break the C API, I propose to discuss that later ;-)
An alternative is to add a tp_fastcall method to PyTypeObject and use a wrapper for tp_call for backward compatibility. This option has also drawbacks. Again, I propose to discuss this later, and first start to focus on the changes that don't break anything ;-)
Quick & dirty microbenchmark: I ran bench_fast-2.py of the issue bpo-26814. It looks like everything is slower :-p In fact, I already noticed this issue and I think that it is fixed with better compilation option: use "./configure --with-lto" and "make profile-opt". See my article: https://haypo.github.io/journey-to-stable-benchmark-deadcode.html
Updated bench_fast-2.py result with Python compiled with PGO+LTO, with benchmark.py fixed to compute average + standard deviation. Only getattr() really seems slower:
----------------------------------+-----------------------+--------------------------
Tests | original | fastcall
----------------------------------+-----------------------+--------------------------
filter | 75.8 us +- 0.1 us () | 78.1 us +- 0.1 us
map | 72.6 us +- 0.1 us () | 71.4 us +- 0.0 us
sorted(list, key=lambda x: x) | 83.7 us +- 0.1 us () | 82.3 us +- 0.3 us
sorted(list) | 14.9 us +- 0.0 us () | 14.7 us +- 0.0 us
b=MyBytes(); bytes(b) | 199 ns +- 2 ns () | 194 ns +- 1 ns
namedtuple.attr | 830 ns +- 20 ns () | 1.09 us +- 0.01 us (+31%)
object.__setattr__(obj, "x", 1) | 133 ns +- 0 ns () | 134 ns +- 1 ns
object.__getattribute__(obj, "x") | 117 ns +- 0 ns () | 115 ns +- 1 ns
getattr(1, "real") | 93.2 ns +- 0.9 ns () | 76.9 ns +- 0.7 ns (-17%)
bounded_pymethod(1, 2) | 73.4 ns +- 0.6 ns () | 70.7 ns +- 0.4 ns
unbound_pymethod(obj, 1, 2) | 74.5 ns +- 0.2 ns () | 71.8 ns +- 0.6 ns
func() | 60.2 ns +- 0.4 ns () | 59.3 ns +- 0.1 ns
func(1, 2, 3) | 74.6 ns +- 0.4 ns () | 72.2 ns +- 0.3 ns
----------------------------------+-----------------------+--------------------------
Total | 249 us () | 248 us
----------------------------------+-----------------------+--------------------------
See bpo-27213. Maybe fast call with keyword arguments would avoid the creation of a dict.
In a first verison of my implementation, I used dictionary items
stored a a list of (key, value) tuples in the same PyObject* C array
than positional parameters.
But in practice, it's very rare in the C code base to have to call a
function with keyword parameters, but most functions expect keyword
parameters as a dict. They are implemented with
PyArg_ParseTupleAndKeywords() which expects a dict.
I spent the last 3 months on making the CPython benchmark suite more stable and enhance my procedure to run benchmarks to ensure that benchmarks are more stable.
elementtree/process: 192 ms +- 3 ms -> 183 ms +- 2 ms: 1.05x faster
elementtree/generate: 225 ms +- 5 ms -> 214 ms +- 4 ms: 1.05x faster
hexiom2/level_25: 21.3 ms +- 0.3 ms -> 20.3 ms +- 0.1 ms: 1.05x faster
Benchmark hidden because not significant (84): (...)
----------------
Most benchmarks are not significant which is expected since fastcall-2.patch is really the most simple patch to start the work on "FASTCALL", it doesn't really implement any optimization, it only adds a new infrastructure to implement new optimizations.
A few benchmarks are faster (only benchmarks at least 5% faster are shown using --min-speed=5).
4 benchmarks are slower, but the slowdown should be temporarily: new optimizations should these benchmarks slower. See the issue bpo-26814 for more a concrete implementation and a lot of benchmark results if you don't trust me :-)
I consider that benchmarks proved that there is no major slowdown, so fastcall-2.patch can be merged to be able to start working on real optimizations.
Benchmarking results look nice, but despite the fact that this patch is only small part of bpo-26814, it looks to me larger that it could be.
The patch includes two parts: adding _PyObject_FastCall() and adding PyObject_CallNoArg() and PyObject_CallArg1(). How large the role of latter functions in the speed up? Can we first just add _PyObject_FastCall() and measure the effect of adding PyObject_CallNoArg() and PyObject_CallArg1() separately? Can existing function PyObject_Call() be optimized to achieve a comparable benefit?
I think that supporting keyword arguments in _PyObject_FastCall() doesn't make much sense now. Calling with keyword arguments adds such much overhead, that it dwarfs the benefit of avoiding the creation of one tuple. I think that the patch can be simpler if drop the support of keyword arguments.
The patch adds two files for one function _PyStack_AsTuple(). I would prefer something like _PyTuple_FromArray(). It could be used in other places, not just in argument parsing.
Benchmarking results look nice, but despite the fact that this patch is
only small part of bpo-26814, it looks to me larger that it could be.
Oh I failed to express my intent. This initial patch is not expected to
introduce any speedup. In fact I noticed major performance regressions on
the CPython benchmark suite using my full fastcall patch. It took me time
to understand that they are more issues with benchmarks than my work. This
minimum patch only adds new functions but don't really use them. I patched
a few functions to show how the new functions can be used. I spent most of
my time just to ensure that the minimum patch doesn't introduce performance
regression.
The patch includes two parts: adding _PyObject_FastCall() and adding
PyObject_CallNoArg() and PyObject_CallArg1(). How large the role of latter
functions in the speed up?
See my remark above, no speedup is expected.
Do you suggest to not add these 2 new functions? Since they are well
defined and simple, I chose to make them public. Their API is nicer than
_PyObject_Call().
Can existing function PyObject_Call() be optimized to achieve a
comparable benefit?
Sorry, I don't understand. This function requires a tuple. The whole
purpose of my patch is to avoid temporary tuples.
In my full patch, PyObject_Call() calls _PyObject_FastCall() in most cases.
I think that supporting keyword arguments in _PyObject_FastCall()
doesn't make much sense now.
Well, I can add support for keyword arguments later and start with an
assertion (fail if they are used). But I really need them in the API, and I
don't want to change to API later.
I plan to add a new METH_FASTCALL calling convention for C functions. I
would prefer to not have two new calling conventions, but use Argument
Clinic to emit efficient code to parse arguments.
Calling with keyword arguments adds such much overhead, that it dwarfs
the benefit of avoiding the creation of one tuple. I think that the patch
can be simpler if drop the support of keyword arguments.
Keyword arguments are optional. Having support for them cost nothing when
they are not used.
The patch adds two files for one function _PyStack_AsTuple(). I would
prefer something like _PyTuple_FromArray(). It could be used in other
places, not just in argument parsing.
I really want to have a "pystack" API. In this patch, the new file looks
useless, but in the full patch there are many functions including a few
complex functions. I prefer to add the file now and complete it later.
I'm limited by Mercurial and our workflow (tools), it would be much easier
to explain my work using a patch serie, but it's not possible to publish a
patch serie...
Yes, I suggest to not add them. The API for calling is already too large.
Internally we can directly use _PyObject_FastCall(), and third party code
should get benefit from optimized PyObject_CallFunctionObjArgs().
> Can existing function PyObject_Call() be optimized to achieve a
> comparable benefit?
Sorry, I don't understand. This function requires a tuple. The whole
purpose of my patch is to avoid temporary tuples.
Sorry, I meant PyObject_CallFunctionObjArgs() and like.
Keyword arguments are optional. Having support for them cost nothing when
they are not used.
My point is that if keyword arguments are used, this is not a fast call, and
should use old calling protocol. The overhead of creating a tuple for args is
dwarfen by the overhead of creating a dict for kwargs and parsing it.
I really want to have a "pystack" API. In this patch, the new file looks
useless, but in the full patch there are many functions including a few
complex functions. I prefer to add the file now and complete it later.
But for now there is no a "pystack" API. What do you want to add? Can it be
added with prefixes PyDict_, PyArg_ or PyEval_? On other side, other code can
get a benefit from using _PyTuple_FromArray().
Here is alternative simplified patch.
_PyStack_AsTuple() is renamed to _PyTuple_FromArray() (-2 new files).
Optimized PyObject_CallFunctionObjArgs(), PyObject_CallMethodObjArgs() and
_PyObject_CallMethodIdObjArgs().
Removed PyObject_CallNoArg() and PyObject_CallArg1(). Invocations are
replaced by PyObject_CallFunctionObjArgs().
Removed support of keyword arguments in _PyObject_FastCall() (saved about
20 lines and few runtime checks in _PyCFunction_FastCall).
Reverted changes in Objects/descrobject.c. They added a regression in
namedtuple attributes access.
ztane commented on Aug 12, 2016
ztanemannequin
Mannequin
More actions
About "I hesitate between the C types "int" and "Py_ssize_t" for nargs. I read once that using "int" can cause performance issues on a loop using "i++" and "data[i]" because the compiler has to handle integer overflow of the int type."
This is true because of -fwrapv, but I believe it is true also for Py_ssize_t which is also of signed type. However, there would be a speed-up achievable by disabling -fwrapv, because only then the i++; data[i] can be safely optimized into *(++data)
> Do you suggest to not add these 2 new functions?
Yes, I suggest to not add them. The API for calling is already too large.
Internally we can directly use _PyObject_FastCall(), and third party code
should get benefit from optimized PyObject_CallFunctionObjArgs().
Well, we can start without them, and see later if it's worth it.
I didn't propose to add new functions to make the code faster, but to
make the API simpler.
I dislike PyEval_CallObjectWithKeywords(func, arg, kw) because it has
a special case if arg is a tuple. If arg is a tuple, the tuple is
unpacked. It already leaded to a complex and very bug in the
implementation of generators! See the issue bpo-21209. I'm not sure that
such use case is well known and understood by everyone...
It's common to call a function with no argument or just one argument,
so I proposed to add an obvious and simple API for these common cases.
Well, again, I will open a new issue to discuss that.
> > Can existing function PyObject_Call() be optimized to achieve a
> > comparable benefit?
> Sorry, I don't understand. This function requires a tuple. The whole
> purpose of my patch is to avoid temporary tuples.
Sorry, I meant PyObject_CallFunctionObjArgs() and like.
> Keyword arguments are optional. Having support for them cost nothing when
> they are not used.
My point is that if keyword arguments are used, this is not a fast call, and
should use old calling protocol. The overhead of creating a tuple for args is
dwarfen by the overhead of creating a dict for kwargs and parsing it.
I'm not sure that I understand your point.
For example, in my full patch, I have a METH_FASTCALL calling
convention for C functions. With this calling convention, a function
accepts positional arguments and keyword arguments. If you don't pass
keyword arguments, the call should be faster according to my
benchmarks.
How do you want to implement METH_FASTCALL if you cannot pass keyword
arguments? Does it mean that METH_FASTCALL can only be used by the
functions which don't accept keyword arguments at all?
It's ok if passing keyword arguments is not faster, but simply as fast
as before, if the "positional arguments only" case is faster, no?
> I really want to have a "pystack" API. In this patch, the new file looks
> useless, but in the full patch there are many functions including a few
> complex functions. I prefer to add the file now and complete it later.
But for now there is no a "pystack" API. What do you want to add?
The problem is that passing keyword arguments as a dict is not the most efficient way due to an overhead of creating a dict. For now keyword arguments are pushed on the stack as interlaced array of keyword names and values. It may be more efficient to push values and names as continuous arrays (bpo-27213). PyArg_ParseTupleAndKeywords() accepts a tuple and a dict, but private function _PyArg_ParseTupleAndKeywordsFast() (bpo-27574) can be changed to accept positional and keyword arguments as continuous arrays: (int nargs, PyObject **args, int nkwargs, PyObject **kwnames, PyObject **kwargs). Therefore we will be forced either to change the signature of _PyObject_FastCall() and the meaning of METH_FASTCALL, or add new _PyObject_FastCallKw() and METH_FASTCALLKW for support fast passing keyword arguments. Or may be add yet _PyObject_FastCallNoKw() for faster passing only positional arguments without an overhead of _PyObject_FastCall(). And make older _PyObject_FastCall() and METH_FASTCALL obsolete.
There is yet one possibility. Argument Clinic can generate a dict that maps keyword argument names to indices of arguments and tie it to a function. External code should map names to indices using this dictionary and pass arguments as just a continuous array to function with METH_FASTCALL (raising an error if some argument is passed as positional and keyword, or if keyword-only argument is passed as positional, etc). In that case the kwargs parameter of _PyObject_FastCall() becomes obsolete too.
Note that PyMethod objects get unpacked into function+self right before the PyFunction_Check(), so the tuple avoidance optimisation also applies to Python method calls.
The main features (_PyFunction_FastCall()) has been merged. Supporting keyword arguments is now handled by other issue (see issue bpo-27830). I close this issue.
Activity
vstinner commentedon May 26, 2016
Since the issue bpo-26814 proved that avoiding the creation of temporary tuples to call Python and C functions makes Python faster (between 2% and 29% depending on the benchmark), I extracted a first "minimal" patch to start merging this work.
The first patch adds new functions:
I hesitate between the C types "int" and "Py_ssize_t" for nargs. I read once that using "int" can cause performance issues on a loop using "i++" and "data[i]" because the compiler has to handle integer overflow of the int type.
The "int" type is also annoying on Windows 64-bit, it causes compiler warnings on downcast like PyTuple_GET_SIZE(co->co_argcount) stored into a C int.
_PyObject_FastCall() avoids the creation of tuple for:
The patch removes the "cache tuple" optimization from property_descr_get(), it uses PyObject_CallArg1() instead. It means that the optimization is (currently) missed in some cases compared to the current code, but the code is safer and simpler.
The patch adds Python/pystack.c which currently only contains _PyStack_AsTuple(), but will contain more code later.
I tried to write the smallest patch, but I started to use PyObject_CallNoArg() and PyObject_CallArg1() when the code already created a tuple at each call: PyObject_CallObject(), call_function_tail() and PyEval_CallObjectWithKeywords().
In the patch, keywords are not used in fast calls. But they will be used later. I prefer to start directly with keywords than changing the calling convention once again later.
--
Later, I will propose other patches to:
So the fast call will be taken in more cases.
--
The long term plan is to slowly use the new FASTCALL calling convention "everywhere". The tricky point are tp_new, tp_init and tp_call attributes of type objects. In the issue bpo-26814, I wrote a patch adding Py_TPFLAGS_FASTNEW, Py_TPFLAGS_FASTINIT and Py_TPFLAGS_FASTCALL flags to use the FASTCALL calling convention for tp_new, tp_init and tp_call. The problem is that calling directly these methods looks common. If we can the calling convention of these methods, it will break the C API, I propose to discuss that later ;-)
An alternative is to add a tp_fastcall method to PyTypeObject and use a wrapper for tp_call for backward compatibility. This option has also drawbacks. Again, I propose to discuss this later, and first start to focus on the changes that don't break anything ;-)
vstinner commentedon May 26, 2016
Quick & dirty microbenchmark: I ran bench_fast-2.py of the issue bpo-26814. It looks like everything is slower :-p In fact, I already noticed this issue and I think that it is fixed with better compilation option: use "./configure --with-lto" and "make profile-opt". See my article:
https://haypo.github.io/journey-to-stable-benchmark-deadcode.html
----------------------------------+-------------+---------------
Tests | original | fastcall
----------------------------------+-------------+---------------
filter | 76.2 us () | 116 us (+52%)
map | 73.6 us () | 102 us (+38%)
sorted(list, key=lambda x: x) | 82 us () | 121 us (+48%)
sorted(list) | 14.7 us () | 17.3 us (+18%)
b=MyBytes(); bytes(b) | 182 ns () | 243 ns (+33%)
namedtuple.attr | 802 ns () | 1.44 us (+80%)
object.__setattr__(obj, "x", 1) | 133 ns () | 166 ns (+25%)
object.__getattribute__(obj, "x") | 116 ns () | 142 ns (+22%)
getattr(1, "real") | 76 ns () | 95 ns (+25%)
bounded_pymethod(1, 2) | 72 ns () | 102 ns (+42%)
unbound_pymethod(obj, 1, 2) | 71 ns () | 99 ns (+38%)
func() | 57 ns () | 81 ns (+41%)
func(1, 2, 3) | 72 ns () | 100 ns (+39%)
----------------------------------+-------------+---------------
Total | 248 us () | 358 us (+44%)
----------------------------------+-------------+---------------
At least, we have a starting point ;-)
vstinner commentedon May 26, 2016
default-May26-13-36-33.log: CPython benchmark suite run using stable config.
Faster (15):
Slower (8):
vstinner commentedon May 26, 2016
Updated bench_fast-2.py result with Python compiled with PGO+LTO, with benchmark.py fixed to compute average + standard deviation. Only getattr() really seems slower:
----------------------------------+-----------------------+--------------------------
Tests | original | fastcall
----------------------------------+-----------------------+--------------------------
filter | 75.8 us +- 0.1 us () | 78.1 us +- 0.1 us
map | 72.6 us +- 0.1 us () | 71.4 us +- 0.0 us
sorted(list, key=lambda x: x) | 83.7 us +- 0.1 us () | 82.3 us +- 0.3 us
sorted(list) | 14.9 us +- 0.0 us () | 14.7 us +- 0.0 us
b=MyBytes(); bytes(b) | 199 ns +- 2 ns () | 194 ns +- 1 ns
namedtuple.attr | 830 ns +- 20 ns () | 1.09 us +- 0.01 us (+31%)
object.__setattr__(obj, "x", 1) | 133 ns +- 0 ns () | 134 ns +- 1 ns
object.__getattribute__(obj, "x") | 117 ns +- 0 ns () | 115 ns +- 1 ns
getattr(1, "real") | 93.2 ns +- 0.9 ns () | 76.9 ns +- 0.7 ns (-17%)
bounded_pymethod(1, 2) | 73.4 ns +- 0.6 ns () | 70.7 ns +- 0.4 ns
unbound_pymethod(obj, 1, 2) | 74.5 ns +- 0.2 ns () | 71.8 ns +- 0.6 ns
func() | 60.2 ns +- 0.4 ns () | 59.3 ns +- 0.1 ns
func(1, 2, 3) | 74.6 ns +- 0.4 ns () | 72.2 ns +- 0.3 ns
----------------------------------+-----------------------+--------------------------
Total | 249 us () | 248 us
----------------------------------+-----------------------+--------------------------
serhiy-storchaka commentedon Jun 9, 2016
See bpo-27213. Maybe fast call with keyword arguments would avoid the creation of a dict.
vstinner commentedon Jun 9, 2016
Serhiy Storchaka added the comment:
In a first verison of my implementation, I used dictionary items
stored a a list of (key, value) tuples in the same PyObject* C array
than positional parameters.
But in practice, it's very rare in the C code base to have to call a
function with keyword parameters, but most functions expect keyword
parameters as a dict. They are implemented with
PyArg_ParseTupleAndKeywords() which expects a dict.
vstinner commentedon Aug 8, 2016
Rebased patch.
vstinner commentedon Aug 8, 2016
(Oops, I removed a broken fastcall-2.patch which didn't include new pystack.c/pystack.h files. It's now fixed in the new fastcall-2.patch.)
vstinner commentedon Aug 8, 2016
I spent the last 3 months on making the CPython benchmark suite more stable and enhance my procedure to run benchmarks to ensure that benchmarks are more stable.
See my articles:
https://haypo-notes.readthedocs.io/microbenchmark.html#my-articles
I forked and enhanced the benchmark suite to use my perf module to run benchmarks in multiple processes:
https://hg.python.org/sandbox/benchmarks_perf
I ran this better benchmark suite on fastcall-2.patch on my laptop. The result is quite good:
----------------
Faster (13):
Benchmark hidden because not significant (84): (...)
----------------
Most benchmarks are not significant which is expected since fastcall-2.patch is really the most simple patch to start the work on "FASTCALL", it doesn't really implement any optimization, it only adds a new infrastructure to implement new optimizations.
A few benchmarks are faster (only benchmarks at least 5% faster are shown using --min-speed=5).
4 benchmarks are slower, but the slowdown should be temporarily: new optimizations should these benchmarks slower. See the issue bpo-26814 for more a concrete implementation and a lot of benchmark results if you don't trust me :-)
I consider that benchmarks proved that there is no major slowdown, so fastcall-2.patch can be merged to be able to start working on real optimizations.
serhiy-storchaka commentedon Aug 9, 2016
Benchmarking results look nice, but despite the fact that this patch is only small part of bpo-26814, it looks to me larger that it could be.
The patch includes two parts: adding _PyObject_FastCall() and adding PyObject_CallNoArg() and PyObject_CallArg1(). How large the role of latter functions in the speed up? Can we first just add _PyObject_FastCall() and measure the effect of adding PyObject_CallNoArg() and PyObject_CallArg1() separately? Can existing function PyObject_Call() be optimized to achieve a comparable benefit?
I think that supporting keyword arguments in _PyObject_FastCall() doesn't make much sense now. Calling with keyword arguments adds such much overhead, that it dwarfs the benefit of avoiding the creation of one tuple. I think that the patch can be simpler if drop the support of keyword arguments.
The patch adds two files for one function _PyStack_AsTuple(). I would prefer something like _PyTuple_FromArray(). It could be used in other places, not just in argument parsing.
vstinner commentedon Aug 9, 2016
Oh I failed to express my intent. This initial patch is not expected to
introduce any speedup. In fact I noticed major performance regressions on
the CPython benchmark suite using my full fastcall patch. It took me time
to understand that they are more issues with benchmarks than my work. This
minimum patch only adds new functions but don't really use them. I patched
a few functions to show how the new functions can be used. I spent most of
my time just to ensure that the minimum patch doesn't introduce performance
regression.
See my remark above, no speedup is expected.
Do you suggest to not add these 2 new functions? Since they are well
defined and simple, I chose to make them public. Their API is nicer than
_PyObject_Call().
Sorry, I don't understand. This function requires a tuple. The whole
purpose of my patch is to avoid temporary tuples.
In my full patch, PyObject_Call() calls _PyObject_FastCall() in most cases.
Well, I can add support for keyword arguments later and start with an
assertion (fail if they are used). But I really need them in the API, and I
don't want to change to API later.
I plan to add a new METH_FASTCALL calling convention for C functions. I
would prefer to not have two new calling conventions, but use Argument
Clinic to emit efficient code to parse arguments.
Keyword arguments are optional. Having support for them cost nothing when
they are not used.
I really want to have a "pystack" API. In this patch, the new file looks
useless, but in the full patch there are many functions including a few
complex functions. I prefer to add the file now and complete it later.
I'm limited by Mercurial and our workflow (tools), it would be much easier
to explain my work using a patch serie, but it's not possible to publish a
patch serie...
serhiy-storchaka commentedon Aug 11, 2016
Yes, I suggest to not add them. The API for calling is already too large.
Internally we can directly use _PyObject_FastCall(), and third party code
should get benefit from optimized PyObject_CallFunctionObjArgs().
Sorry, I meant PyObject_CallFunctionObjArgs() and like.
My point is that if keyword arguments are used, this is not a fast call, and
should use old calling protocol. The overhead of creating a tuple for args is
dwarfen by the overhead of creating a dict for kwargs and parsing it.
But for now there is no a "pystack" API. What do you want to add? Can it be
added with prefixes PyDict_, PyArg_ or PyEval_? On other side, other code can
get a benefit from using _PyTuple_FromArray().
Here is alternative simplified patch.
_PyObject_CallMethodIdObjArgs().
replaced by PyObject_CallFunctionObjArgs().
20 lines and few runtime checks in _PyCFunction_FastCall).
namedtuple attributes access.
ztane commentedon Aug 12, 2016
About "I hesitate between the C types "int" and "Py_ssize_t" for nargs. I read once that using "int" can cause performance issues on a loop using "i++" and "data[i]" because the compiler has to handle integer overflow of the int type."
This is true because of -fwrapv, but I believe it is true also for Py_ssize_t which is also of signed type. However, there would be a speed-up achievable by disabling -fwrapv, because only then the i++; data[i] can be safely optimized into *(++data)
vstinner commentedon Aug 16, 2016
Serhiy Storchaka added the comment:
Well, we can start without them, and see later if it's worth it.
I didn't propose to add new functions to make the code faster, but to
make the API simpler.
I dislike PyEval_CallObjectWithKeywords(func, arg, kw) because it has
a special case if arg is a tuple. If arg is a tuple, the tuple is
unpacked. It already leaded to a complex and very bug in the
implementation of generators! See the issue bpo-21209. I'm not sure that
such use case is well known and understood by everyone...
It's common to call a function with no argument or just one argument,
so I proposed to add an obvious and simple API for these common cases.
Well, again, I will open a new issue to discuss that.
Yes, my full patch does optimize these functions:
https://hg.python.org/sandbox/fastcall/file/2dc558e01e66/Objects/abstract.c#l2523
I'm not sure that I understand your point.
For example, in my full patch, I have a METH_FASTCALL calling
convention for C functions. With this calling convention, a function
accepts positional arguments and keyword arguments. If you don't pass
keyword arguments, the call should be faster according to my
benchmarks.
How do you want to implement METH_FASTCALL if you cannot pass keyword
arguments? Does it mean that METH_FASTCALL can only be used by the
functions which don't accept keyword arguments at all?
It's ok if passing keyword arguments is not faster, but simply as fast
as before, if the "positional arguments only" case is faster, no?
See my fastcall branch:
https://hg.python.org/sandbox/fastcall/file/2dc558e01e66/Include/pystack.h
https://hg.python.org/sandbox/fastcall/file/2dc558e01e66/Python/pystack.c
All these functions are private. They are used internally to implement
all functions of the Python C API to call functions.
Ah? Maybe you should open a different issue for that.
I prefer to have an API specific to build a "stack" to call functions.
My full patch does optimize "everything", it's deliberate to start
with something useless but short.
Ah? What is the regression?
5 remaining items
python-dev commentedon Aug 19, 2016
New changeset 73b00fb1dc9d by Victor Stinner in branch 'default':
Cleanup call_method() and call_maybe()
https://hg.python.org/cpython/rev/73b00fb1dc9d
New changeset 8e085070ab28 by Victor Stinner in branch 'default':
call_method() and call_maybe() now use fast call
https://hg.python.org/cpython/rev/8e085070ab28
New changeset 2d2bc1906b5b by Victor Stinner in branch 'default':
Issue bpo-27128: Cleanup slot_sq_item()
https://hg.python.org/cpython/rev/2d2bc1906b5b
New changeset 6eb586b85fa1 by Victor Stinner in branch 'default':
Issue bpo-27128: slot_sq_item() uses fast call
https://hg.python.org/cpython/rev/6eb586b85fa1
New changeset 605a42a50496 by Victor Stinner in branch 'default':
Issue bpo-27128: Cleanup slot_nb_bool()
https://hg.python.org/cpython/rev/605a42a50496
New changeset 6a21b6599692 by Victor Stinner in branch 'default':
slot_nb_bool() now uses fast call
https://hg.python.org/cpython/rev/6a21b6599692
New changeset 45d2b5c12b19 by Victor Stinner in branch 'default':
slot_tp_iter() now uses fast call
https://hg.python.org/cpython/rev/45d2b5c12b19
New changeset 124d5d0ef81f by Victor Stinner in branch 'default':
calliter_iternext() now uses fast call
https://hg.python.org/cpython/rev/124d5d0ef81f
New changeset 71c22e592a9b by Victor Stinner in branch 'default':
keyobject_richcompare() now uses fast call
https://hg.python.org/cpython/rev/71c22e592a9b
python-dev commentedon Aug 19, 2016
New changeset 3ab32f7add6e by Victor Stinner in branch 'default':
Issue bpo-27128: _pickle uses fast call
https://hg.python.org/cpython/rev/3ab32f7add6e
vstinner commentedon Aug 19, 2016
Ok, I updated the most simple forms of function calls.
I will open new issues for more complex calls and more sensible parts of
the code like ceval.c.
Buildbots seem to be happy.
python-dev commentedon Aug 19, 2016
New changeset c2af917bde71 by Victor Stinner in branch 'default':
PyFile_WriteObject() now uses fast call
https://hg.python.org/cpython/rev/c2af917bde71
New changeset 0da1ce362d15 by Victor Stinner in branch 'default':
import_name() now uses fast call
https://hg.python.org/cpython/rev/0da1ce362d15
New changeset e5b24f595235 by Victor Stinner in branch 'default':
PyErr_PrintEx() now uses fast call
https://hg.python.org/cpython/rev/e5b24f595235
New changeset 154f78d387f9 by Victor Stinner in branch 'default':
call_trampoline() now uses fast call
https://hg.python.org/cpython/rev/154f78d387f9
New changeset 351b987d6d1c by Victor Stinner in branch 'default':
sys_pyfile_write_unicode() now uses fast call
https://hg.python.org/cpython/rev/351b987d6d1c
New changeset abb93035ebb7 by Victor Stinner in branch 'default':
_elementtree: deepcopy() now uses fast call
https://hg.python.org/cpython/rev/abb93035ebb7
New changeset 2954d2aa4c90 by Victor Stinner in branch 'default':
pattern_subx() now uses fast call
https://hg.python.org/cpython/rev/2954d2aa4c90
vstinner commentedon Aug 20, 2016
I created two new issues:
scoder commentedon Aug 22, 2016
FYI: I copied your (no-kwargs) implementation over into Cython and I get around 17% faster calls to Python functions with 2 positional arguments.
vstinner commentedon Aug 22, 2016
Hey, cool! It's always cool to get performance enhancement without having to break the C API nor having to modify source code :-)
What do you mean by "I copied your (no-kwargs) implementation"? The whole giant patch? Or just a few changes? Which changes?
python-dev commentedon Aug 22, 2016
New changeset 7dd85b19c873 by Victor Stinner in branch 'default':
Optimize call to Python function without argument
https://hg.python.org/cpython/rev/7dd85b19c873
serhiy-storchaka commentedon Aug 22, 2016
The problem is that passing keyword arguments as a dict is not the most efficient way due to an overhead of creating a dict. For now keyword arguments are pushed on the stack as interlaced array of keyword names and values. It may be more efficient to push values and names as continuous arrays (bpo-27213). PyArg_ParseTupleAndKeywords() accepts a tuple and a dict, but private function _PyArg_ParseTupleAndKeywordsFast() (bpo-27574) can be changed to accept positional and keyword arguments as continuous arrays: (int nargs, PyObject **args, int nkwargs, PyObject **kwnames, PyObject **kwargs). Therefore we will be forced either to change the signature of _PyObject_FastCall() and the meaning of METH_FASTCALL, or add new _PyObject_FastCallKw() and METH_FASTCALLKW for support fast passing keyword arguments. Or may be add yet _PyObject_FastCallNoKw() for faster passing only positional arguments without an overhead of _PyObject_FastCall(). And make older _PyObject_FastCall() and METH_FASTCALL obsolete.
There is yet one possibility. Argument Clinic can generate a dict that maps keyword argument names to indices of arguments and tie it to a function. External code should map names to indices using this dictionary and pass arguments as just a continuous array to function with METH_FASTCALL (raising an error if some argument is passed as positional and keyword, or if keyword-only argument is passed as positional, etc). In that case the kwargs parameter of _PyObject_FastCall() becomes obsolete too.
vstinner commentedon Aug 22, 2016
Serhiy: I "moved" your msg273365 to the issue bpo-27809.
scoder commentedon Aug 22, 2016
I copied what you committed into CPython for _PyFunction_FastCall():
cython/cython@8f3d3bd
and then enabled its usage in a couple of places:
cython/cython@a3cfec8
especially for all function/method calls that we generate for user code:
cython/cython@a51df33
Note that PyMethod objects get unpacked into function+self right before the PyFunction_Check(), so the tuple avoidance optimisation also applies to Python method calls.
vstinner commentedon Aug 22, 2016
Ok, I see much better with concrete commits. I'm really happy that
Cython also benefits from these enhancements.
Note: handling keywords is likely to change quickly ;-)
vstinner commentedon Sep 1, 2016
The main features (_PyFunction_FastCall()) has been merged. Supporting keyword arguments is now handled by other issue (see issue bpo-27830). I close this issue.