Slide 1

Slide 1 text

Accelerating Your Python Code: Cython and PyPy Greg Price google:greg+price Solano Labs ConFoo 2013-02-28 1 / 59

Slide 2

Slide 2 text

1 2 / 59

Slide 3

Slide 3 text

Problem: CPython is slow 3 / 59

Slide 4

Slide 4 text

Solution? Rewrite bits in C 4 / 59

Slide 5

Slide 5 text

“The universal speed-up is rewriting small bits of code in C. Do this only when all else fails.” —Guido, 2012 5 / 59

Slide 6

Slide 6 text

6 / 59

Slide 7

Slide 7 text

# from json/encoding.py in stdlib # 23 lines def py_encode_basestring_ascii(s): """Return an ASCII-only JSON representation of a Python string """ if isinstance(s, str) and HAS_UTF8.search(s) is not None: s = s.decode(’utf-8’) def replace(match): s = match.group(0) try: return ESCAPE_DCT[s] except KeyError: n = ord(s) if n < 0x10000: return ’\\u{0:04x}’.format(n) #return ’\\u%04x’ % (n,) else: # surrogate pair n -= 0x10000 s1 = 0xd800 | ((n >> 10) & 0x3ff) s2 = 0xdc00 | (n & 0x3ff) return ’\\u{0:04x}\\u{1:04x}’.format(s1, s2) #return ’\\u%04x\\u%04x’ % (s1, s2) return ’"’ + str(ESCAPE_ASCII.sub(replace, s)) + ’"’ 7 / 59

Slide 8

Slide 8 text

/* Same function in stdlib, from _json.c */ /* 200 lines total: 85 lines this, plus 57 unicode, plus 58 helpers */ static PyObject * ascii_escape_str(PyObject *pystr) { /* Take a PyString pystr and return a new ASCII-only escaped PyString */ Py_ssize_t i; Py_ssize_t input_chars; Py_ssize_t output_size; Py_ssize_t chars; PyObject *rval; char *output; char *input_str; input_chars = PyString_GET_SIZE(pystr); input_str = PyString_AS_STRING(pystr); /* Fast path for a string that’s already ASCII */ for (i = 0; i < input_chars; i++) { Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i]; if (!S_CHAR(c)) { /* If we have to escape something, scan the string for unicode */ Py_ssize_t j; for (j = i; j < input_chars; j++) { c = (Py_UNICODE)(unsigned char)input_str[j]; if (c > 0x7f) { /* We hit a non-ASCII character, bail to unicode mode */ PyObject *uni; uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict"); if (uni == NULL) { return NULL; } rval = ascii_escape_unicode(uni); Py_DECREF(uni); return rval; } } 8 / 59

Slide 9

Slide 9 text

$ python Python 2.7.3 (default, Aug 1 2012, 05:16:07) >>> import MySQLdb >>> MySQLdb.__version__ ’1.2.3’ >>> d = {’a’: 3} >>> def bad(i, _): ... d.clear() ... d.update((j,j) for j in xrange(300)) ... >>> MySQLdb._mysql.escape_dict(d, {int: bad}) Segmentation fault (core dumped) 9 / 59

Slide 10

Slide 10 text

Solution scope C API Cython PyPy 10 / 59

Slide 11

Slide 11 text

/1 11 / 59

Slide 12

Slide 12 text

2: Why? 12 / 59

Slide 13

Slide 13 text

naive implementations of dynamic languages 13 / 59

Slide 14

Slide 14 text

Python 14 / 59

Slide 15

Slide 15 text

Python Ruby 14 / 59

Slide 16

Slide 16 text

Python Ruby PHP 14 / 59

Slide 17

Slide 17 text

Python Ruby PHP Lua 14 / 59

Slide 18

Slide 18 text

Python Ruby PHP Lua JavaScript 14 / 59

Slide 19

Slide 19 text

/* CPython 2.7, Python/ceval.c */ PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) { /* ... */ for (;;) { /* ... */ switch (opcode) { /* ... */ case BINARY_ADD: w = POP(); v = TOP(); if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { /* INLINE: int + int */ register long a, b, i; a = PyInt_AS_LONG(v); b = PyInt_AS_LONG(w); i = (long)((unsigned long)a + b); if ((i^a) < 0 && (i^b) < 0) goto slow_add; x = PyInt_FromLong(i); } else if (PyString_CheckExact(v) && PyString_CheckExact(w)) { x = string_concatenate(v, w, f, next_instr); goto skip_decref_vx; } else { slow_add: x = PyNumber_Add(v, w); } /* Objects/abstract.c */ PyObject * PyNumber_Add(PyObject *v, PyObject *w) { PyObject *result = binary_op1(v, w, NB_SLOT(nb_add)); if (result == Py_NotImplemented) { PySequenceMethods *m = v->ob_type->tp_as_sequence; Py_DECREF(result); if (m && m->sq_concat) { return (*m->sq_concat)(v, w); } result = binop_type_error(v, w, "+"); } return result; } 15 / 59

Slide 20

Slide 20 text

int x; x = x + 2; add $0x2,%ebx 16 / 59

Slide 21

Slide 21 text

CPython naive dynamic C static 17 / 59

Slide 22

Slide 22 text

CPython naive dynamic C static Cython static, Python-like 17 / 59

Slide 23

Slide 23 text

void work(thing *th, void (*cb)(void *, thing_part *), void *data) { ... cb(data, &th->part) ... } 18 / 59

Slide 24

Slide 24 text

CPython naive dynamic C static Cython static, Python-like PyPy Python, smart dynamic 19 / 59

Slide 25

Slide 25 text

Solution scope C API Cython PyPy 20 / 59

Slide 26

Slide 26 text

/2: Why? 21 / 59

Slide 27

Slide 27 text

3: PyPy 22 / 59

Slide 28

Slide 28 text

Same language, new interpreter 23 / 59

Slide 29

Slide 29 text

Fast interpreter 24 / 59

Slide 30

Slide 30 text

Smaller is better. Ratio of PyPy time to CPython 2.7 time. http://speed.pypy.org/ 25 / 59

Slide 31

Slide 31 text

print sum(i*i for i in xrange(10000000 CPython 1.866s PyPy 1.377s 26 / 59

Slide 32

Slide 32 text

def f(x): return x*x print sum(f(i) for i in xrange(1000000 CPython 2.546s PyPy 1.398s 27 / 59

Slide 33

Slide 33 text

/* CPython 2.7, Python/ceval.c */ PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) { /* ... */ for (;;) { /* ... */ switch (opcode) { /* ... */ case BINARY_ADD: w = POP(); v = TOP(); if (PyInt_CheckExact(v) && PyInt_CheckExact(w)) { /* INLINE: int + int */ register long a, b, i; a = PyInt_AS_LONG(v); b = PyInt_AS_LONG(w); i = (long)((unsigned long)a + b); if ((i^a) < 0 && (i^b) < 0) goto slow_add; x = PyInt_FromLong(i); } else if (PyString_CheckExact(v) && PyString_CheckExact(w)) { x = string_concatenate(v, w, f, next_instr); goto skip_decref_vx; } else { slow_add: x = PyNumber_Add(v, w); } /* Objects/abstract.c */ PyObject * PyNumber_Add(PyObject *v, PyObject *w) { PyObject *result = binary_op1(v, w, NB_SLOT(nb_add)); if (result == Py_NotImplemented) { PySequenceMethods *m = v->ob_type->tp_as_sequence; Py_DECREF(result); if (m && m->sq_concat) { return (*m->sq_concat)(v, w); } result = binop_type_error(v, w, "+"); } return result; } 28 / 59

Slide 34

Slide 34 text

# pypy/module/pypyjit/interp_jit.py # (simplified for slide) def dispatch(self, pycode, next_instr, ec): try: while True: co_code = pycode.co_code next_instr = self.handle_bytecode( co_code, next_instr, ec) except ExitFrame: return self.popvalue() 29 / 59

Slide 35

Slide 35 text

# pypy/module/pypyjit/interp_jit.py # (simplified for slide) def dispatch(self, pycode, next_instr, ec): try: while True: pypyjitdriver.jit_merge_point(ec=ec, frame=self, next_instr=next_instr, pycode=pycode) co_code = pycode.co_code next_instr = self.handle_bytecode( co_code, next_instr, ec) except ExitFrame: return self.popvalue() 30 / 59

Slide 36

Slide 36 text

Faster than C? char x[44]; sprintf(x, "%d %d", i, i); "%d %d" % (i, i) 31 / 59

Slide 37

Slide 37 text

Faster than C? char x[44]; sprintf(x, "%d %d", i, i); "%d %d" % (i, i) C 1.0x CPython 0.16x PyPy 1.9x 31 / 59

Slide 38

Slide 38 text

Extension modules 32 / 59

Slide 39

Slide 39 text

Extension modules # a simple Django app, newsdiffs.org $ grep python.*so$ /proc/19044/maps b6560000-b6570000 r-xp 00000000 fc:01 397463 /usr/lib/python2.7/lib-dynload/_sqlite3.so b6570000-b6571000 r--p 0000f000 fc:01 397463 /usr/lib/python2.7/lib-dynload/_sqlite3.so b6571000-b6573000 rw-p 00010000 fc:01 397463 /usr/lib/python2.7/lib-dynload/_sqlite3.so b6f45000-b6f58000 r-xp 00000000 fc:01 397446 /usr/lib/python2.7/lib-dynload/datetime.so b6f58000-b6f59000 r--p 00012000 fc:01 397446 /usr/lib/python2.7/lib-dynload/datetime.so $ perl -lne ’print $1 if (m{python.*?/(.*.so)})’ \ /proc/19044/maps | uniq lib-dynload/_sqlite3.so lib-dynload/datetime.so dist-packages/simplejson/_speedups.so lib-dynload/termios.so lib-dynload/_heapq.so 33 / 59

Slide 40

Slide 40 text

/3: PyPy 34 / 59

Slide 41

Slide 41 text

4: Cython 35 / 59

Slide 42

Slide 42 text

# hello.py def hello(): print ’Hello world’ hello() 36 / 59

Slide 43

Slide 43 text

Easy way $ python Python 2.7.3 (default, Aug 1 2012) >>> import pyximport >>> pyximport.install() >>> import hello Hello world >>> 37 / 59

Slide 44

Slide 44 text

Digging deeper $ cython hello.py $ ls hello.py hello.c 38 / 59

Slide 45

Slide 45 text

/* "hello.py":1 * def hello(): # <<<<<<<<<<<<<< * print ’Hello world’ * */ static PyObject *__pyx_pf_5hello_hello(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ static PyMethodDef __pyx_mdef_5hello_hello = {__Pyx_NAMESTR("hello"), (PyCFunction)__pyx_pf_5hello_hello, METH_NOARGS, __Pyx_DOCSTR(0)}; static PyObject *__pyx_pf_5hello_hello(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations int __pyx_lineno = 0; const char *__pyx_filename = NULL; int __pyx_clineno = 0; __Pyx_RefNannySetupContext("hello"); __pyx_self = __pyx_self; /* "hello.py":2 * def hello(): * print ’Hello world’ # <<<<<<<<<<<<<< * * hello() */ if (__Pyx_PrintOne(0, ((PyObject *)__pyx_kp_s_1)) < 0) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 2; __pyx_clineno = __LINE__; goto __pyx_L1_error;} __pyx_r = Py_None; __Pyx_INCREF(Py_None); goto __pyx_L0; __pyx_L1_error:; __Pyx_AddTraceback("hello.hello", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); __Pyx_RefNannyFinishContext(); return __pyx_r; } 39 / 59

Slide 46

Slide 46 text

def f(n): t = 0 for i in xrange(n): t += i-2*i+i+1 return t print f(10000000) 40 / 59

Slide 47

Slide 47 text

$ cython --embed sum.py $ gcc -I/usr/include/python2.7/ \ sum.c -lpython2.7 -o sum $ ./sum 10000000 41 / 59

Slide 48

Slide 48 text

CPython 1.3s PyPy 0.057s Cython 1.7s 42 / 59

Slide 49

Slide 49 text

import cython @cython.locals(n=cython.int, i=cython.int) def f(n): t = 0 for i in xrange(n): t += i-2*i+i+1 return t print f(10000000) 43 / 59

Slide 50

Slide 50 text

CPython 1.3s PyPy 0.057s Cython v1 1.7s Cython v2 0.37s 44 / 59

Slide 51

Slide 51 text

import cython @cython.locals(n=cython.int, i=cython.int, t=cython.int) def f(n): t = 0 for i in xrange(n): t += i-2*i+i+1 return t print f(10000000) 45 / 59

Slide 52

Slide 52 text

CPython 1.3s PyPy 0.057s Cython v1 1.7s Cython v2 0.37s Cython v3 0.047s 46 / 59

Slide 53

Slide 53 text

lxml 47 / 59

Slide 54

Slide 54 text

/4: Cython 48 / 59

Slide 55

Slide 55 text

End http://pypy.org/ google:pypy+papers http://cython.org/ Slides: https://joind.in/7937 49 / 59

Slide 56

Slide 56 text

50 / 59

Slide 57

Slide 57 text

51 / 59

Slide 58

Slide 58 text

52 / 59

Slide 59

Slide 59 text

53 / 59

Slide 60

Slide 60 text

54 / 59

Slide 61

Slide 61 text

§ 1: Just so 55 / 59

Slide 62

Slide 62 text

56 / 59

Slide 63

Slide 63 text

________________ test_list _________________ def test_list(): > assert [1, 2] == [3, 2] E assert [1, 2] == [3, 2] E At index 0 diff: 1 != 3 test_diffs.py:11: AssertionError 57 / 59

Slide 64

Slide 64 text

(CC-BY, “soham pablo” on Flickr) 58 / 59

Slide 65

Slide 65 text

End http://pytest.org/ http://tddium.com/ 59 / 59