Author: Antonio Cuni <[email protected]>
Branch: extradoc
Changeset: r5904:fbd92c0e1a20
Date: 2018-09-20 10:53 +0200
http://bitbucket.org/pypy/extradoc/changeset/fbd92c0e1a20/

Log:    add a section about bad C APIs

diff --git a/blog/draft/2018-09-cpyext/cpyext.rst 
b/blog/draft/2018-09-cpyext/cpyext.rst
--- a/blog/draft/2018-09-cpyext/cpyext.rst
+++ b/blog/draft/2018-09-cpyext/cpyext.rst
@@ -87,10 +87,6 @@
 To understand some of cpyext challenges, you need to have at least a rough
 idea of how the PyPy GC works.
 
-XXX: maybe the following section is too detailed and not really necessary to
-understand cpyext? We could simplify it by saying "PyPy uses a generational
-GC, objects can move".
-
 Contrarily to the popular belief, the "Garbage Collector" is not only about
 collecting garbage: instead, it is generally responsible of all memory
 management, including allocation and deallocation.
@@ -136,7 +132,7 @@
 pass them to C extensions.  We surely need a way to handle that.
 
 
-`PyObject*` in PyPy
+``PyObject*`` in PyPy
 ---------------------
 
 Another challenge is that sometimes, ``PyObject*`` structs are not completely
@@ -151,7 +147,7 @@
 So, we have two issues so far: objects which can move, and incompatible
 low-level layouts. ``cpyext`` solves both by decoupling the RPython and the C
 representations: we have two "views" of the same entity, depending on whether
-we are in the PyPy world (the moving ``W_Root`` subclass) or in the C world
+we are in the PyPy world (the movable ``W_Root`` subclass) or in the C world
 (the non-movable ``PyObject*``).
 
 ``PyObject*`` are created lazily, only when they are actually needed: the
@@ -243,7 +239,7 @@
 
 About the GIL, we won't dig into details of `how it is handled in cpyext`_:
 for the purpose of this post, it is enough to know that whenever we enter the
-C land, we store the current theead id into a global variable which is
+C land, we store the current thread id into a global variable which is
 accessible also from C; conversely, whenever we go back from RPython to C, we
 restore this value to 0.
 
@@ -368,8 +364,8 @@
 
 The solution is simple: rewrite as much as we can in C instead of RPython, so
 to avoid unnecessary roundtrips: this was the topic of most of the Cape Town
-sprint and resulted in the ``cpyext-avoid-roundtrip``, which was eventually
-merged_.
+sprint and resulted in the ``cpyext-avoid-roundtrip`` branch, which was
+eventually merged_.
 
 Of course, it is not possible to move **everything** to C: there are still
 operations which need to be implemented in RPython. For example, think of
@@ -443,8 +439,8 @@
 managed, etc., and we can assume that allocation costs are the same than on
 CPython.
 
-However, as soon as we return these ``PyObject*`` Python, we need to allocate
-its ``W_Root`` equivalent: if you do it in a small loop like in the example
+As soon as we return these ``PyObject*`` to Python, we need to allocate
+theirs ``W_Root`` equivalent: if you do it in a small loop like in the example
 above, you end up allocating all these ``W_Root`` inside the nursery, which is
 a good thing since allocation is super fast (see the section above about the
 PyPy GC).
@@ -471,6 +467,56 @@
 C API quirks
 --------------------
 
-XXX explain why borrowed references are a problem for us; possibly link to: 
https://pythoncapi.readthedocs.io/bad_api.html#borrowed-references
+Finally, there is another source of slowdown which is beyond our control: some
+parts of the CPython C API are badly designed and expose some of the
+implementation details of CPython.
 
-the calling convention is inefficient: why do I have to allocate a PyTuple* of 
PyObect*, just to unwrap them immediately?
+The major example is reference counting: the ``Py_INCREF`` / ``Py_DECREF`` API
+is designed in such a way which forces other implementation to emulate
+refcounting even in presence of other GC management schemes, as explained
+above.
+
+Another example is borrowed references: there are API functions which **do
+not** incref an object before returning it, e.g. `PyList_GetItem`_.  This is
+done for performance reasons because we can avoid a whole incref/decref pair,
+if the caller needs to handle the returned item only temporarily: the item is
+kept alive because it is in the list anyway.
+
+For PyPy this is a challenge: thanks to `list strategies`_, often lists are
+represented in a compact way: e.g. a list containing only integers is stored
+as a C array of ``long``.  How to implement ``PyList_GetItem``? We cannot
+simply create a ``PyObject*`` on the fly, because the caller will never decref
+it and it will result in a memory leak.
+
+The current solution is very inefficient: basically, the first time we do a
+``PyList_GetItem``, we convert_ the **whole** list to a list of
+``PyObject*``. This is bad in two ways: the first is that we potentially pay a
+lot of unneeded conversion cost in case we will never access the other items
+of the list; the second is that by doing that we lose all the performance
+benefit granted by the original list strategy, making it slower even for the
+rest of pure-python code which will manipulate the list later.
+
+``PyList_GetItem`` is an example of a bad API because it assumes that the list
+is implemented as an array of ``PyObject*``: after all, in order to return a
+borrowed reference, we need a reference to borrow, don't we?
+
+Fortunately, (some) CPython developers are aware of these problems, and there
+is an ongoing project to `design a better C API`_ which aims to fix exactly
+this kind of problems.
+
+Nonetheless, in the meantime we still need to implement the current
+half-broken APIs: there is no easy solutions for that, and it is likely that
+we will always need to pay some performance penalty in order to implement them
+correctly.
+
+However, what we could potentially do is to provide alternative functions
+which do the same job but are more PyPy friendly: for example, we could think
+of implementing ``PyList_GetItemNonBorrowed`` or something like that: then, C
+extensions could choose to use it (possibly hidden inside some macro and
+``#ifdef``) if they want to be fast on PyPy.
+
+
+.. _`PyList_GetItem`: 
https://docs.python.org/2/c-api/list.html#c.PyList_GetItem
+.. _`list strategies`: 
https://morepypy.blogspot.com/2011/10/more-compact-lists-with-list-strategies.html
+.. _convert: 
https://bitbucket.org/pypy/pypy/src/b9bbd6c0933349cbdbfe2b884a68a16ad16c3a8a/pypy/module/cpyext/listobject.py#lines-28
+.. _`design a better C API`: https://pythoncapi.readthedocs.io/
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to