Slides for the Cluj.py meetup where we explored the inner workings of CPython, the reference implementation of Python. Includes examples of writing a C extension to Python, and introduces Cython - ultimately the sanest way of writing C extensions.
Also check out the code samples on GitHub: https://github.com/trustyou/meetups/tree/master/python-c
2. Goals of todayâs talk
â Look behind the scenes of the CPython interpreter -
gain insights into âhow Python worksâ
â Explore the CPython C API
â Build a Python extension in C
â Introduction to Cython
3. Who are we?
â For each hotel on the
planet, provide a
summary of all reviews
â Expertise:
â NLP
â Machine Learning
â Big Data
â Clients: âŠ
4.
5. TrustYou Tech Stack
Batch Layer
â Hadoop (HDP 2.1)
â Python
â Pig
â Luigi
Service Layer
â PostgreSQL
â MongoDB
â Redis
â Cassandra
Data Data Queries
Hadoop cluster (100 nodes) Application machines
6. Letâs dive in! Assigning an integer
a = 4 PyObject* a =
PyInt_FromLong(4);
// what's the
difference to
int a = 4?
Documentation: PyInt_FromLong
7. List item access
x = xs[i] PyObject* x =
PyList_GetItem(xs,
i);
Documentation: PyList_GetItem
9. Calling a function
foo(1337, "bar") // argument list
PyObject *args = Py_BuildValue
("is", 1337, "bar");
// make call
PyObject_CallObject(foo,
args);
// release arguments
Py_DECREF(args);
Documentation: Py_BuildValue,
PyObject_CallObject
10. Whatâs the CPython C API?
â API to manipulate Python objects, and interact with
Python code, from C/C++
â Purpose: Extend Python with new modules/types
â Why?
11. CPython internals
def slangify(s):
return s + ", yo!"
C API
Compiler Interpreter
>>> slangify("hey")
'hey, yo!'
|x00x00dx01x00x17S
Not true for Jython, IronPython, PyPy, Stackless âŠ
15. Writing in C
â No OOP :
typedef struct { /* ... */ } complexType;
void fun(complexType* obj, int x, char* y) {
// ...
}
â Macros for code generation:
#define SWAP(x,y) {int tmp = x; x = y; y = tmp;}
SWAP(a, b);
16. Writing in C
â Manual memory management:
â C: static, stack, malloc/free
â Python C API: Reference counting
â No exceptions
â Error handling via returning values
â CPython: return null; signals an error
17. Reference Counting
void Py_INCREF(PyObject *o)
Increment the reference count for object o. The object must not be NULL; if you arenât sure that it isnât NULL, use
Py_XINCREF().
= I want to hold on to this object and use it again after a while*
*) any interaction with Python interpreter that may invalidate my reference
void Py_DECREF(PyObject *o)
Decrement the reference count for object o. The object must not be NULL; if you arenât sure that it isnât NULL, use
Py_XDECREF(). If the reference count reaches zero, the objectâs typeâs deallocation function (which must not
beNULL) is invoked.
= Iâm done, and donât care if the object is discarded at this call
See documentation
18. Anatomy of a refcount bug
void buggy(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0); // borrowed ref.
PyList_SetItem(list, 1, PyInt_FromLong(0L)); // calls
destructor of previous element
PyObject_Print(item, stdout, 0); // BUG!
}
27. Why go through all this trouble?
â Performance
â C extensions & Cython optimize CPU-bound code
(vs. memory-bound, IO-bound)
â Pareto principle: 20% of the code responsible for
80% of the runtime
â Also: Interfacing with existing C/C++ code
28. Is my Python code performance-critical?
import cProfile, pstats, sys
pr = cProfile.Profile()
pr.enable()
setup()
# run code you want to profile
pr.disable()
stats = pstats.Stats(pr, stream=sys.stdout).sort_stats("time")
stats.print_stats()
32. Pythonic QuickSort
def quicksort(xs):
if len(xs) <= 1:
return xs
middle = len(xs) / 2
pivot = xs[middle]
del xs[middle]
left, right = [], []
for x in xs:
append_to = left if x < pivot else right
append_to.append(x)
return quicksort(left) + [pivot] + quicksort(right)
33. Results: Python vs. C extension
Pythonic QuickSort: 2.0s
C extension module: 0.092s
35. Adding integers in Cython
# add.pyx
def add(i, j):
return i + j
# main.py
import pyximport; pyximport.install()
import add
if __name__ == "__main__":
print add.add(1, 1337)
36. What is Cython?
â Compiles Python to C code
â âSupersetâ of Python: Accepts type annotations to
compile more efficient code (optional!)
cdef int i = 2
â No reference counting, error handling, boilerplate âŠ
plus nicer compiling workflows
38. cdef partition(xs, int left, int right, int pivot_index):
cdef int pivot = xs[pivot_index]
cdef int el
xs[pivot_index], xs[right] = xs[right], xs[pivot_index]
pivot_index = left
for i in xrange(left, right):
el = xs[i]
if el <= pivot:
xs[i], xs[pivot_index] = xs[pivot_index], xs[i]
pivot_index += 1
xs[pivot_index], xs[right] = xs[right], xs[pivot_index]
return pivot_index
def quicksort(xs, left=0, right=None):
if right is None:
right = len(xs) - 1
if left < right:
middle = (left + right) / 2
pivot_index = partition(xs, left, right, middle)
quicksort(xs, left, pivot_index - 1)
quicksort(xs, pivot_index + 1, right)
39. Results:
Pythonic QuickSort: 2.0s
C extension module: 0.092s
Cython QuickSort (unchanged): 0.82s
Cython QuickSort (C-like): 0.37s
â Unscientific result. Cython can be faster than hand-written
C extensions!
40. Further Reading on Cython
See code samples on â OâReilly Book
TrustYou GitHub account:
https://github.
com/trustyou/meetups/tre
e/master/python-c
41. TrustYou wants you!
We offer positions
in Cluj & Munich:
â Data engineer
â Application developer
â Crawling engineer
Write me at swenz@trustyou.net, check out our website,
or see you at the next meetup!
44. Anatomy of a memory leak
void buggier()
{
PyObject *lst = PyList_New(10);
return Py_BuildValue("Oi", lst, 10); // increments refcount
}
// read the doc carefully before using *any* C API function