{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# The Need for Speed: An Introduction to Cython\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Do you really need the speed?\n", "\n", "* Write your Python program.\n", "* Ensure it executes correctly and does what it is supposed to.\n", "* Is it fast enough?\n", "* If yes: Ignore the rest of the presentation.\n", "* If no:\n", " 1. Get it right.\n", " 2. Test it's right.\n", " 3. Profile if slow.\n", " 4. Optimise (C/C++ or use Cython and save yourself the pain).\n", " 5. Repeat from 2.\n", " \n", "> We *should forget* about small efficiencies, say about 97% of the time: **premature optimization is the root of all evil**.\n", "\n", "> Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only **after** that code has been identified.\n", "\n", "

**Donald Knuth**

" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# What is Cython?\n", "\n", "* Cython is a superset of the Python language.\n", "* Cython translates Python code to equivalent C code.\n", "* This code is executed within the CPython runtime environment, but at the speed of compiled C and with the ability to call directly into C libraries. \n", "* At the same time, it keeps the original interface of the Python source code, which makes it directly usable from Python code.\n", "* This enables Cython's two major use cases:\n", " 1. Extending the CPython interpreter with fast binary modules.\n", " 2. Interfacing Python code with external C libraries.\n", "* While Cython can compile (most) regular Python code, the generated C code usually gains major (and sometime impressive) speed improvements from optional static type declarations for both Python and C types." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Why is Python slow?\n", "\n", "* Contrary to popular belief, Python is **not** slow because it is interpreted. You could use Cython to convert Python to C and compile it and you would gain minimal speedups.\n", "* The main reason that Python is slow is that it's **dynamically** typed.\n", "* At the time the program executes, the interpreter *doesn't know the type of the variables* that are defined. Everything is an *object*, everything is in a *box* which results in **boxing–unboxing overhead**.\n", "* There is also significant overhead on attribute lookups. For instance, calling a method `f` on an object `a` will cause possible lookups in `__dict__`, calls to `__getattr__`, etc, then finally call `__call__` on the callable object `f`. This process cannot be optimised for the reason metioned above." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Building Cython code\n", "\n", "Cython code must, unlike Python, be compiled. This happens in two stages:\n", "* A `.pyx` file is compiled by Cython to a `.c` file, containing the code of a Python extension module,\n", "* The `.c` file is compiled by a `C` compiler to a `.so` file (or `.pyd` on Windows) which can be imported directly into a Python session.\n", " \n", "There are several ways to build Cython code:\n", "* Write a distutils (or setuptools) `setup.py`.\n", "* Use `pyximport`, importing Cython `.pyx` files as if they were `.py` files (using distutils to compile and build in the background).\n", "* Run the cython command-line utility manually to produce the `.c` file from the `.pyx` file, then manually compiling the `.c` file into a shared object library or DLL suitable for import from Python. (These manual steps are mostly for debugging and experimentation.)\n", "* Use the [Jupyter notebook](http://cython.readthedocs.io/en/latest/src/quickstart/build.html#jupyter), which allows Cython code inline." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Cython workflow\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Cython Hello World\n", "\n", "1. Create a `helloworld.pyx`:\n", "```Python\n", "print(\"Hello World\")\n", "```\n", "2. Create a `setup.py`, which is like a Python makefile:\n", "```Python\n", "from distutils.core import setup\n", "from Cython.Build import cythonize\n", "setup(ext_modules = cythonize('helloworld.pyx'))\n", "```\n", "3. Build the Cython file:\n", "```Bash\n", "python setup.py build_ext --inplace\n", "```\n", "4. This will leave a file in your local directory called `helloworld.so` in unix or `helloworld.pyd` in Windows. To use this file, simply import it as if it was a regular python module:\n", "```Python\n", ">>> import helloworld\n", "Hello World\n", "```" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# `pyximport`: Cython compilation the easy way\n", "\n", "If your module doesn't require any extra C libraries or a special build setup, then you can use the `pyximport` module to load `.pyx` files directly on import, without having to write a `setup.py` file:\n", "```Python\n", ">>> import pyximport; pyximport.install()\n", ">>> import helloworld\n", "Hello World\n", "```\n", "It is also possible to compile new `.py` modules that are being imported (including the standard library and installed packages). For using this feature, just tell that to `pyximport`:\n", "```Python\n", ">>> pyximport.install(pyimport=True)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Cython file types\n", "\n", "There are three file types in Cython:\n", "1. **Implementation** files carry a `.pyx` suffix. Can contain basically anything Cythonic.\n", "2. **Definition** files carry a `.pxd` suffix. They work like C header files and contain Cython declarations (and sometimes code sections), which are only meant for inclusion by Cython modules. A `.pxd` file is imported into a `.pyx` module by using the `cimport` keyword. Use `cimport` as you would Python’s import statement, to access these files from other definition or implementation files.\n", "3. **Include** files which carry a `.pxi` suffix. Can contain any Cythonic code, because the entire file is textually embedded at the location you prescribe. Include the `.pxi` file with an `include` statement like: `include \"spamstuff.pxi\"`\n", "`cimport`\n" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Variable and type definitions\n", "\n", "The `cdef` statement is used to declare C variables, either local or module-level\n", "```Cython\n", "cdef int i, j, k\n", "cdef float f, g[42], *h\n", "```\n", "and C `struct`, `union` or `enum` types:\n", "```Cython\n", "cdef struct Grail:\n", " int age\n", " float volume\n", "\n", "cdef union Food:\n", " char *spam\n", " float *eggs\n", "\n", "cdef enum CheeseType:\n", " cheddar, edam,\n", " camembert\n", "\n", "cdef enum CheeseState:\n", " hard = 1\n", " soft = 2\n", " runny = 3\n", "```\n", "Grouping multiple C declarations:\n", "```Cython\n", "cdef:\n", " struct Spam:\n", " int tons\n", "\n", " int i\n", " float a\n", " Spam *p\n", "\n", " void f(Spam *s):\n", " print(s.tons, \"Tons of spam\")\n", "```" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Functions and Methods\n", "\n", "1. `def`: callable from Python.\n", " * Declared with the `def` statement.\n", " * Called with Python objects.\n", " * Return Python objects. \n", "
\n", "2. `cdef`: callable from Cython or C.\n", " * Declared with the `cdef` statement.\n", " * Called with either Python objects or C values.\n", " * Return either Python objects or C values. \n", "
\n", "3. `cpdef`: callable from Python, Cython or C.\n", " * Declared with the `cpdef` statement.\n", " * Can be called from anywhere, because it uses a little Cython magic.\n", " * Uses the faster C calling conventions when being called from other Cython code." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Arrays, `numpy` and memoryviews\n", "\n", "* Python has a builtin array module supporting dynamic 1-dimensional arrays of primitive types. It is possible to access the underlying C array of a Python array from within Cython.\n", "* Before typed memoryviews, Cython had different syntax for working efficiently with `numpy` arrays and other buffer-supporting objects. This original buffer syntax is still in use, but it has been superseded by typed memoryviews, which provide more features and cleaner syntax.\n", "\n", "```Cython\n", "cimport numpy as np\n", "\n", "def convolve(np.ndarray[double, ndim=2] f, np.ndarray[double, ndim=2] g):\n", " cdef:\n", " np.ndarray[double, ndim=2] h\n", " # ...other static declarations\n", " h = np.zeros((xmax, ymax), dtype=np.double_t)\n", "```\n", "\n", "* Typed memoryviews allow efficient access to memory buffers (such as `numpy` arrays, C arrays, or C++ vectors) *without any Python overhead*. They can work with a wide range of buffer-supporting objects: `numpy` arrays, Python memoryview objects, `array.array` objects, and any other type that supports the new buffer protocol. They can also work with C arrays. They\n", "are therefore more general than the `numpy` array buffer syntax, which is restricted to work with `numpy` arrays only.\n", "\n", "```Cython\n", "def convolve(double[:, ::1] f, double[:, ::1] g):\n", " cdef:\n", " double[:, ::1] h\n", " # ...\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Typed memoryviews\n", "\n", "\n", "\n", "```Cython\n", "cdef int[:, :, :] mv # a 3D typed memoryview, can be assigned to...\n", "cdef int a[3][3][3] # 1: a C-array:\n", "a = np.zeros((10,20,30), dtype=np.int32) # 2: a NumPy-array:\n", "cdef int[:, :, :] a = b # 3: another memoryview\n", "\n", "# indexing like NumPy, but faster, at C-level.\n", "mv[1,2,0] # -> integer\n", "# slicing like numpy, but faster.\n", "mv[10] == mv[10, :, :] == mv[10,...] # -> a new memoryview.\n", "```" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Extension types (`cdef` classes)\n", "\n", "* Somewhat restricted compared to Python classes, but generally more memory efficient and faster. The main difference is that they use a C struct to store their fields and methods instead of a Python dict.\n", "* This allows them to store arbitrary C types in their fields without requiring a Python wrapper for them, and to access fields and methods directly at the C level without passing through a Python dictionary lookup.\n", "* Normal Python classes can inherit from `cdef` classes, but not the other way around. They can also inherit from any number of Python classes and extension types, both in Cython code and pure Python code.\n", "* We cannot use `cdef` and `cpdef` to define methods on non-`cdef` classes.\n", "\n", "\n", "```Cython\n", "cdef class Particle: # Creates a new type, like list, int, dict\n", " cdef float *vel, *pos # attributes stored in instance’s struct\n", " cdef public float m # expose variable to Python.\n", " \n", " def __cinit__(self, float m, p, v): # allocate C-level data,\n", " self.m = m # called before __init__()\n", " self.vel = malloc(3*sizeof(float))\n", " self.pos = malloc(3*sizeof(float))\n", " # check if vel or pos are NULL...\n", " for i in range(3):\n", " self.vel[i] = v[i]; self.pos[i] = p[i]\n", " \n", " cpdef apply_impulse(self, f, t): # methods can be def, cdef, or cpdef.\n", " # ...\n", " \n", " def __dealloc__(self): # deallocate C arrays, called when gc’d.\n", " if self.vel: free(self.vel)\n", " if self.pos: free(self.pos)\n", "```" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Loops\n", "\n", "* Cython supports `for` and `while` loops without modification.\n", "* Loops often occupy the majority of a program's runtime, so we can translate Python looping constructs into efficient C analogues.\n", "* If the index variable `i` and range argument `n` are dynamically typed, Cython may not be able to generate a fast C for loop:\n", "\n", "```Cython\n", " n = 100\n", " for i in range(n):\n", " # ...\n", "```\n", "\n", "* When looping over a range call, we should type the range argument as a C integer:\n", "\n", "```Cython\n", "cdef int n\n", "for i in range(n):\n", " # ...\n", "```\n", "\n", "* Cython will automatically type the loop index variable `i` as an `int` as well, provided we *do not use the index in an expression in the loop body*, because it cannot automatically infer whether the operation will overflow, and refuses to infer a C integer type. If we are certain the expression will not cause integer overflow, we statically type the index variable:\n", "\n", "```Cython\n", "cdef int i, n\n", "for i in range(n):\n", " a[i] = i + 1\n", "```\n", "\n", "* For efficient loops over containers, consider converting the container to a C++ equivalent container or using typed memoryviews." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# Automatic type inference\n", "\n", "* Cython infers variable types only when doing so cannot change the semantics of the code.\n", "* When enabling `infer_types`, we are taking responsibility to ensure that integer operations do not overflow and that semantics do not change from the untyped version.\n", "\n", "```Cython\n", "def automatic_inference():\n", " i = 1 # may not be representable as C long\n", " d = 2.0 # inferred that d can be stored as double\n", " c = 3 + 4j\n", " r = i * d + c\n", " return r\n", "\n", "cimport cython\n", "\n", "@cython.infer_types(True)\n", "def forced_inference():\n", " i = 1 # inferred as C long, we are taking responsibility for overflows\n", " d = 2.0\n", " c = 3 + 4j\n", " r = i * d + c\n", " return r\n", "```" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "%load_ext cython" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", " \n", " Cython: _cython_magic_02d5c366cee2bfe4761d328976839617.pyx\n", " \n", " \n", "\n", "\n", "

Generated by Cython 0.25.2

\n", "

\n", " Yellow lines hint at Python interaction.
\n", " Click on a line that starts with a \"+\" to see the C code that Cython generated for it.\n", "

\n", "
+01: def py_fib(n):
\n", "
/* Python wrapper */\n",
       "static PyObject *__pyx_pw_46_cython_magic_02d5c366cee2bfe4761d328976839617_1py_fib(PyObject *__pyx_self, PyObject *__pyx_v_n); /*proto*/\n",
       "static PyMethodDef __pyx_mdef_46_cython_magic_02d5c366cee2bfe4761d328976839617_1py_fib = {\"py_fib\", (PyCFunction)__pyx_pw_46_cython_magic_02d5c366cee2bfe4761d328976839617_1py_fib, METH_O, 0};\n",
       "static PyObject *__pyx_pw_46_cython_magic_02d5c366cee2bfe4761d328976839617_1py_fib(PyObject *__pyx_self, PyObject *__pyx_v_n) {\n",
       "  PyObject *__pyx_r = 0;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"py_fib (wrapper)\", 0);\n",
       "  __pyx_r = __pyx_pf_46_cython_magic_02d5c366cee2bfe4761d328976839617_py_fib(__pyx_self, ((PyObject *)__pyx_v_n));\n",
       "\n",
       "  /* function exit code */\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "static PyObject *__pyx_pf_46_cython_magic_02d5c366cee2bfe4761d328976839617_py_fib(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_n) {\n",
       "  PyObject *__pyx_v_a = NULL;\n",
       "  PyObject *__pyx_v_b = NULL;\n",
       "  CYTHON_UNUSED PyObject *__pyx_v_i = NULL;\n",
       "  PyObject *__pyx_r = NULL;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"py_fib\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_XDECREF(__pyx_t_1);\n",
       "  __Pyx_XDECREF(__pyx_t_2);\n",
       "  __Pyx_XDECREF(__pyx_t_5);\n",
       "  __Pyx_AddTraceback(\"_cython_magic_02d5c366cee2bfe4761d328976839617.py_fib\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __pyx_r = NULL;\n",
       "  __pyx_L0:;\n",
       "  __Pyx_XDECREF(__pyx_v_a);\n",
       "  __Pyx_XDECREF(__pyx_v_b);\n",
       "  __Pyx_XDECREF(__pyx_v_i);\n",
       "  __Pyx_XGIVEREF(__pyx_r);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "/* … */\n",
       "  __pyx_tuple_ = PyTuple_Pack(4, __pyx_n_s_n, __pyx_n_s_a, __pyx_n_s_b, __pyx_n_s_i); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_tuple_);\n",
       "  __Pyx_GIVEREF(__pyx_tuple_);\n",
       "/* … */\n",
       "  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_02d5c366cee2bfe4761d328976839617_1py_fib, NULL, __pyx_n_s_cython_magic_02d5c366cee2bfe476); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  if (PyDict_SetItem(__pyx_d, __pyx_n_s_py_fib, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_codeobj__2 = (PyObject*)__Pyx_PyCode_New(1, 0, 4, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple_, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_C_Users_telis_ipython_cython__cy, __pyx_n_s_py_fib, 1, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__2)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "
+02:     a, b = 1, 1
\n", "
  __pyx_t_1 = __pyx_int_1;\n",
       "  __Pyx_INCREF(__pyx_t_1);\n",
       "  __pyx_t_2 = __pyx_int_1;\n",
       "  __Pyx_INCREF(__pyx_t_2);\n",
       "  __pyx_v_a = __pyx_t_1;\n",
       "  __pyx_t_1 = 0;\n",
       "  __pyx_v_b = __pyx_t_2;\n",
       "  __pyx_t_2 = 0;\n",
       "
+03:     for i in range(n):
\n", "
  __pyx_t_2 = PyTuple_New(1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_2);\n",
       "  __Pyx_INCREF(__pyx_v_n);\n",
       "  __Pyx_GIVEREF(__pyx_v_n);\n",
       "  PyTuple_SET_ITEM(__pyx_t_2, 0, __pyx_v_n);\n",
       "  __pyx_t_1 = __Pyx_PyObject_Call(__pyx_builtin_range, __pyx_t_2, NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;\n",
       "  if (likely(PyList_CheckExact(__pyx_t_1)) || PyTuple_CheckExact(__pyx_t_1)) {\n",
       "    __pyx_t_2 = __pyx_t_1; __Pyx_INCREF(__pyx_t_2); __pyx_t_3 = 0;\n",
       "    __pyx_t_4 = NULL;\n",
       "  } else {\n",
       "    __pyx_t_3 = -1; __pyx_t_2 = PyObject_GetIter(__pyx_t_1); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "    __Pyx_GOTREF(__pyx_t_2);\n",
       "    __pyx_t_4 = Py_TYPE(__pyx_t_2)->tp_iternext; if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "  }\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  for (;;) {\n",
       "    if (likely(!__pyx_t_4)) {\n",
       "      if (likely(PyList_CheckExact(__pyx_t_2))) {\n",
       "        if (__pyx_t_3 >= PyList_GET_SIZE(__pyx_t_2)) break;\n",
       "        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS\n",
       "        __pyx_t_1 = PyList_GET_ITEM(__pyx_t_2, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "        #else\n",
       "        __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "        __Pyx_GOTREF(__pyx_t_1);\n",
       "        #endif\n",
       "      } else {\n",
       "        if (__pyx_t_3 >= PyTuple_GET_SIZE(__pyx_t_2)) break;\n",
       "        #if CYTHON_ASSUME_SAFE_MACROS && !CYTHON_AVOID_BORROWED_REFS\n",
       "        __pyx_t_1 = PyTuple_GET_ITEM(__pyx_t_2, __pyx_t_3); __Pyx_INCREF(__pyx_t_1); __pyx_t_3++; if (unlikely(0 < 0)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "        #else\n",
       "        __pyx_t_1 = PySequence_ITEM(__pyx_t_2, __pyx_t_3); __pyx_t_3++; if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "        __Pyx_GOTREF(__pyx_t_1);\n",
       "        #endif\n",
       "      }\n",
       "    } else {\n",
       "      __pyx_t_1 = __pyx_t_4(__pyx_t_2);\n",
       "      if (unlikely(!__pyx_t_1)) {\n",
       "        PyObject* exc_type = PyErr_Occurred();\n",
       "        if (exc_type) {\n",
       "          if (likely(exc_type == PyExc_StopIteration || PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration))) PyErr_Clear();\n",
       "          else __PYX_ERR(0, 3, __pyx_L1_error)\n",
       "        }\n",
       "        break;\n",
       "      }\n",
       "      __Pyx_GOTREF(__pyx_t_1);\n",
       "    }\n",
       "    __Pyx_XDECREF_SET(__pyx_v_i, __pyx_t_1);\n",
       "    __pyx_t_1 = 0;\n",
       "/* … */\n",
       "  }\n",
       "  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;\n",
       "
+04:         a, b = a + b, a
\n", "
    __pyx_t_1 = PyNumber_Add(__pyx_v_a, __pyx_v_b); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 4, __pyx_L1_error)\n",
       "    __Pyx_GOTREF(__pyx_t_1);\n",
       "    __pyx_t_5 = __pyx_v_a;\n",
       "    __Pyx_INCREF(__pyx_t_5);\n",
       "    __Pyx_DECREF_SET(__pyx_v_a, __pyx_t_1);\n",
       "    __pyx_t_1 = 0;\n",
       "    __Pyx_DECREF_SET(__pyx_v_b, __pyx_t_5);\n",
       "    __pyx_t_5 = 0;\n",
       "
+05:     return a
\n", "
  __Pyx_XDECREF(__pyx_r);\n",
       "  __Pyx_INCREF(__pyx_v_a);\n",
       "  __pyx_r = __pyx_v_a;\n",
       "  goto __pyx_L0;\n",
       "
 06: 
\n", "
+07: def cy_fib(int n):
\n", "
/* Python wrapper */\n",
       "static PyObject *__pyx_pw_46_cython_magic_02d5c366cee2bfe4761d328976839617_3cy_fib(PyObject *__pyx_self, PyObject *__pyx_arg_n); /*proto*/\n",
       "static PyMethodDef __pyx_mdef_46_cython_magic_02d5c366cee2bfe4761d328976839617_3cy_fib = {\"cy_fib\", (PyCFunction)__pyx_pw_46_cython_magic_02d5c366cee2bfe4761d328976839617_3cy_fib, METH_O, 0};\n",
       "static PyObject *__pyx_pw_46_cython_magic_02d5c366cee2bfe4761d328976839617_3cy_fib(PyObject *__pyx_self, PyObject *__pyx_arg_n) {\n",
       "  int __pyx_v_n;\n",
       "  PyObject *__pyx_r = 0;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cy_fib (wrapper)\", 0);\n",
       "  assert(__pyx_arg_n); {\n",
       "    __pyx_v_n = __Pyx_PyInt_As_int(__pyx_arg_n); if (unlikely((__pyx_v_n == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 7, __pyx_L3_error)\n",
       "  }\n",
       "  goto __pyx_L4_argument_unpacking_done;\n",
       "  __pyx_L3_error:;\n",
       "  __Pyx_AddTraceback(\"_cython_magic_02d5c366cee2bfe4761d328976839617.cy_fib\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return NULL;\n",
       "  __pyx_L4_argument_unpacking_done:;\n",
       "  __pyx_r = __pyx_pf_46_cython_magic_02d5c366cee2bfe4761d328976839617_2cy_fib(__pyx_self, ((int)__pyx_v_n));\n",
       "\n",
       "  /* function exit code */\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "static PyObject *__pyx_pf_46_cython_magic_02d5c366cee2bfe4761d328976839617_2cy_fib(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_n) {\n",
       "  CYTHON_UNUSED int __pyx_v_i;\n",
       "  int __pyx_v_a;\n",
       "  int __pyx_v_b;\n",
       "  PyObject *__pyx_r = NULL;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cy_fib\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_XDECREF(__pyx_t_5);\n",
       "  __Pyx_AddTraceback(\"_cython_magic_02d5c366cee2bfe4761d328976839617.cy_fib\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __pyx_r = NULL;\n",
       "  __pyx_L0:;\n",
       "  __Pyx_XGIVEREF(__pyx_r);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "/* … */\n",
       "  __pyx_tuple__3 = PyTuple_Pack(5, __pyx_n_s_n, __pyx_n_s_n, __pyx_n_s_i, __pyx_n_s_a, __pyx_n_s_b); if (unlikely(!__pyx_tuple__3)) __PYX_ERR(0, 7, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_tuple__3);\n",
       "  __Pyx_GIVEREF(__pyx_tuple__3);\n",
       "/* … */\n",
       "  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_02d5c366cee2bfe4761d328976839617_3cy_fib, NULL, __pyx_n_s_cython_magic_02d5c366cee2bfe476); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 7, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  if (PyDict_SetItem(__pyx_d, __pyx_n_s_cy_fib, __pyx_t_1) < 0) __PYX_ERR(0, 7, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "
 08:     cdef int i, a, b
\n", "
+09:     a, b = 1, 1
\n", "
  __pyx_t_1 = 1;\n",
       "  __pyx_t_2 = 1;\n",
       "  __pyx_v_a = __pyx_t_1;\n",
       "  __pyx_v_b = __pyx_t_2;\n",
       "
+10:     for i in range(n):
\n", "
  __pyx_t_2 = __pyx_v_n;\n",
       "  for (__pyx_t_1 = 0; __pyx_t_1 < __pyx_t_2; __pyx_t_1+=1) {\n",
       "    __pyx_v_i = __pyx_t_1;\n",
       "
+11:         a, b = a + b, a
\n", "
    __pyx_t_3 = (__pyx_v_a + __pyx_v_b);\n",
       "    __pyx_t_4 = __pyx_v_a;\n",
       "    __pyx_v_a = __pyx_t_3;\n",
       "    __pyx_v_b = __pyx_t_4;\n",
       "  }\n",
       "
+12:     return a
\n", "
  __Pyx_XDECREF(__pyx_r);\n",
       "  __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_v_a); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 12, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_5);\n",
       "  __pyx_r = __pyx_t_5;\n",
       "  __pyx_t_5 = 0;\n",
       "  goto __pyx_L0;\n",
       "
" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%cython -a\n", "def py_fib(n):\n", " a, b = 1, 1\n", " for i in range(n):\n", " a, b = a + b, a\n", " return a\n", "\n", "def cy_fib(int n):\n", " cdef int i, a, b\n", " a, b = 1, 1\n", " for i in range(n):\n", " a, b = a + b, a\n", " return a" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "43.5 µs ± 758 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)\n", "533 ns ± 10.9 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)\n" ] } ], "source": [ "%timeit -n 10000 -r 3 py_fib(1000)\n", "%timeit -n 10000 -r 3 cy_fib(1000)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]\n" ] } ], "source": [ "def py_primes(kmax):\n", " \"\"\"Calculation of prime numbers in standard Python syntax.\"\"\"\n", "\n", " p = [0] * 1000\n", " result = []\n", " if kmax > 1000:\n", " kmax = 1000\n", " k = 0\n", " n = 2\n", " while k < kmax:\n", " i = 0\n", " while i < k and n % p[i] != 0:\n", " i = i + 1\n", " if i == k:\n", " p[k] = n\n", " k = k + 1\n", " result.append(n)\n", " n = n + 1\n", " return result\n", "\n", "print(py_primes(10))" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]\n" ] }, { "data": { "text/html": [ "\n", "\n", "\n", "\n", " \n", " Cython: _cython_magic_0ced8f83c564d45ab78425accda8735b.pyx\n", " \n", " \n", "\n", "\n", "

Generated by Cython 0.25.2

\n", "

\n", " Yellow lines hint at Python interaction.
\n", " Click on a line that starts with a \"+\" to see the C code that Cython generated for it.\n", "

\n", "
+01: def cy_primes(int kmax):
\n", "
/* Python wrapper */\n",
       "static PyObject *__pyx_pw_46_cython_magic_0ced8f83c564d45ab78425accda8735b_1cy_primes(PyObject *__pyx_self, PyObject *__pyx_arg_kmax); /*proto*/\n",
       "static char __pyx_doc_46_cython_magic_0ced8f83c564d45ab78425accda8735b_cy_primes[] = \"Calculation of prime numbers in Cython.\";\n",
       "static PyMethodDef __pyx_mdef_46_cython_magic_0ced8f83c564d45ab78425accda8735b_1cy_primes = {\"cy_primes\", (PyCFunction)__pyx_pw_46_cython_magic_0ced8f83c564d45ab78425accda8735b_1cy_primes, METH_O, __pyx_doc_46_cython_magic_0ced8f83c564d45ab78425accda8735b_cy_primes};\n",
       "static PyObject *__pyx_pw_46_cython_magic_0ced8f83c564d45ab78425accda8735b_1cy_primes(PyObject *__pyx_self, PyObject *__pyx_arg_kmax) {\n",
       "  int __pyx_v_kmax;\n",
       "  PyObject *__pyx_r = 0;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cy_primes (wrapper)\", 0);\n",
       "  assert(__pyx_arg_kmax); {\n",
       "    __pyx_v_kmax = __Pyx_PyInt_As_int(__pyx_arg_kmax); if (unlikely((__pyx_v_kmax == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 1, __pyx_L3_error)\n",
       "  }\n",
       "  goto __pyx_L4_argument_unpacking_done;\n",
       "  __pyx_L3_error:;\n",
       "  __Pyx_AddTraceback(\"_cython_magic_0ced8f83c564d45ab78425accda8735b.cy_primes\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return NULL;\n",
       "  __pyx_L4_argument_unpacking_done:;\n",
       "  __pyx_r = __pyx_pf_46_cython_magic_0ced8f83c564d45ab78425accda8735b_cy_primes(__pyx_self, ((int)__pyx_v_kmax));\n",
       "\n",
       "  /* function exit code */\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "static PyObject *__pyx_pf_46_cython_magic_0ced8f83c564d45ab78425accda8735b_cy_primes(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_kmax) {\n",
       "  int __pyx_v_n;\n",
       "  int __pyx_v_k;\n",
       "  int __pyx_v_i;\n",
       "  int __pyx_v_p[0x3E8];\n",
       "  PyObject *__pyx_v_result = NULL;\n",
       "  PyObject *__pyx_r = NULL;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cy_primes\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_XDECREF(__pyx_t_1);\n",
       "  __Pyx_AddTraceback(\"_cython_magic_0ced8f83c564d45ab78425accda8735b.cy_primes\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __pyx_r = NULL;\n",
       "  __pyx_L0:;\n",
       "  __Pyx_XDECREF(__pyx_v_result);\n",
       "  __Pyx_XGIVEREF(__pyx_r);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "/* … */\n",
       "  __pyx_tuple_ = PyTuple_Pack(7, __pyx_n_s_kmax, __pyx_n_s_kmax, __pyx_n_s_n, __pyx_n_s_k, __pyx_n_s_i, __pyx_n_s_p, __pyx_n_s_result); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_tuple_);\n",
       "  __Pyx_GIVEREF(__pyx_tuple_);\n",
       "/* … */\n",
       "  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_0ced8f83c564d45ab78425accda8735b_1cy_primes, NULL, __pyx_n_s_cython_magic_0ced8f83c564d45ab7); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  if (PyDict_SetItem(__pyx_d, __pyx_n_s_cy_primes, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_codeobj__2 = (PyObject*)__Pyx_PyCode_New(1, 0, 7, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple_, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_C_Users_telis_ipython_cython__cy, __pyx_n_s_cy_primes, 1, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__2)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "
 02:     """Calculation of prime numbers in Cython."""
\n", "
 03:     cdef int n, k, i
\n", "
 04:     cdef int p[1000]
\n", "
+05:     result = []
\n", "
  __pyx_t_1 = PyList_New(0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 5, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_v_result = ((PyObject*)__pyx_t_1);\n",
       "  __pyx_t_1 = 0;\n",
       "
+06:     if kmax > 1000:
\n", "
  __pyx_t_2 = ((__pyx_v_kmax > 0x3E8) != 0);\n",
       "  if (__pyx_t_2) {\n",
       "/* … */\n",
       "  }\n",
       "
+07:         kmax = 10000
\n", "
    __pyx_v_kmax = 0x2710;\n",
       "
+08:     k = 0
\n", "
  __pyx_v_k = 0;\n",
       "
+09:     n = 2
\n", "
  __pyx_v_n = 2;\n",
       "
+10:     while k < kmax:
\n", "
  while (1) {\n",
       "    __pyx_t_2 = ((__pyx_v_k < __pyx_v_kmax) != 0);\n",
       "    if (!__pyx_t_2) break;\n",
       "
+11:         i = 0
\n", "
    __pyx_v_i = 0;\n",
       "
+12:         while i < k and n % p[i] != 0:
\n", "
    while (1) {\n",
       "      __pyx_t_3 = ((__pyx_v_i < __pyx_v_k) != 0);\n",
       "      if (__pyx_t_3) {\n",
       "      } else {\n",
       "        __pyx_t_2 = __pyx_t_3;\n",
       "        goto __pyx_L8_bool_binop_done;\n",
       "      }\n",
       "      if (unlikely((__pyx_v_p[__pyx_v_i]) == 0)) {\n",
       "        PyErr_SetString(PyExc_ZeroDivisionError, \"integer division or modulo by zero\");\n",
       "        __PYX_ERR(0, 12, __pyx_L1_error)\n",
       "      }\n",
       "      __pyx_t_3 = ((__Pyx_mod_int(__pyx_v_n, (__pyx_v_p[__pyx_v_i])) != 0) != 0);\n",
       "      __pyx_t_2 = __pyx_t_3;\n",
       "      __pyx_L8_bool_binop_done:;\n",
       "      if (!__pyx_t_2) break;\n",
       "
+13:             i = i + 1
\n", "
      __pyx_v_i = (__pyx_v_i + 1);\n",
       "    }\n",
       "
+14:         if i == k:
\n", "
    __pyx_t_2 = ((__pyx_v_i == __pyx_v_k) != 0);\n",
       "    if (__pyx_t_2) {\n",
       "/* … */\n",
       "    }\n",
       "
+15:             p[k] = n
\n", "
      (__pyx_v_p[__pyx_v_k]) = __pyx_v_n;\n",
       "
+16:             k = k + 1
\n", "
      __pyx_v_k = (__pyx_v_k + 1);\n",
       "
+17:             result.append(n)
\n", "
      __pyx_t_1 = __Pyx_PyInt_From_int(__pyx_v_n); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 17, __pyx_L1_error)\n",
       "      __Pyx_GOTREF(__pyx_t_1);\n",
       "      __pyx_t_4 = __Pyx_PyList_Append(__pyx_v_result, __pyx_t_1); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 17, __pyx_L1_error)\n",
       "      __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "
+18:         n = n + 1
\n", "
    __pyx_v_n = (__pyx_v_n + 1);\n",
       "  }\n",
       "
+19:     return result
\n", "
  __Pyx_XDECREF(__pyx_r);\n",
       "  __Pyx_INCREF(__pyx_v_result);\n",
       "  __pyx_r = __pyx_v_result;\n",
       "  goto __pyx_L0;\n",
       "
 20: 
\n", "
+21: print(cy_primes(10))
\n", "
  __pyx_t_1 = __Pyx_GetModuleGlobalName(__pyx_n_s_cy_primes); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 21, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_t_1, __pyx_tuple__3, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 21, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_2);\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __pyx_t_1 = PyTuple_New(1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 21, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  __Pyx_GIVEREF(__pyx_t_2);\n",
       "  PyTuple_SET_ITEM(__pyx_t_1, 0, __pyx_t_2);\n",
       "  __pyx_t_2 = 0;\n",
       "  __pyx_t_2 = __Pyx_PyObject_Call(__pyx_builtin_print, __pyx_t_1, NULL); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 21, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_2);\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "  __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0;\n",
       "
" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%cython -a\n", "def cy_primes(int kmax):\n", " \"\"\"Calculation of prime numbers in Cython.\"\"\"\n", " cdef int n, k, i\n", " cdef int p[1000]\n", " result = []\n", " if kmax > 1000:\n", " kmax = 10000\n", " k = 0\n", " n = 2\n", " while k < kmax:\n", " i = 0\n", " while i < k and n % p[i] != 0:\n", " i = i + 1\n", " if i == k:\n", " p[k] = n\n", " k = k + 1\n", " result.append(n)\n", " n = n + 1\n", " return result\n", "\n", "print(cy_primes(10))" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "40 µs ± 304 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)\n", "1.21 µs ± 32.8 ns per loop (mean ± std. dev. of 3 runs, 10000 loops each)\n" ] } ], "source": [ "%timeit -n 10000 -r 3 py_primes(20)\n", "%timeit -n 10000 -r 3 cy_primes(20)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# The Levenshtein distance between two words is the minimum number of single-character edits\n", "# (insertions, deletions or substitutions) required to change one word into the other.\n", "\n", "def py_levenshtein(s, t):\n", " return py_lev(s, len(s), t, len(t))\n", "\n", "def py_lev(s, len_s, t, len_t):\n", " if len_s == 0 or len_t == 0:\n", " return len_s or len_t\n", " return min(py_lev(s, len_s-1, t, len_t) + 1,\n", " py_lev(s, len_s, t, len_t-1) + 1,\n", " py_lev(s, len_s-1, t, len_t-1) + py_cost(s, len_s, t, len_t)) \n", "\n", "def py_cost(s, len_s, t, len_t):\n", " return s[len_s-1] != t[len_t-1]" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "\n", "\n", " \n", " Cython: _cython_magic_09178397f426d86b92ac0473b4709898.pyx\n", " \n", " \n", "\n", "\n", "

Generated by Cython 0.25.2

\n", "

\n", " Yellow lines hint at Python interaction.
\n", " Click on a line that starts with a \"+\" to see the C code that Cython generated for it.\n", "

\n", "
+01: def cy_levenshtein(s, t):
\n", "
/* Python wrapper */\n",
       "static PyObject *__pyx_pw_46_cython_magic_09178397f426d86b92ac0473b4709898_1cy_levenshtein(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/\n",
       "static PyMethodDef __pyx_mdef_46_cython_magic_09178397f426d86b92ac0473b4709898_1cy_levenshtein = {\"cy_levenshtein\", (PyCFunction)__pyx_pw_46_cython_magic_09178397f426d86b92ac0473b4709898_1cy_levenshtein, METH_VARARGS|METH_KEYWORDS, 0};\n",
       "static PyObject *__pyx_pw_46_cython_magic_09178397f426d86b92ac0473b4709898_1cy_levenshtein(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {\n",
       "  PyObject *__pyx_v_s = 0;\n",
       "  PyObject *__pyx_v_t = 0;\n",
       "  PyObject *__pyx_r = 0;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cy_levenshtein (wrapper)\", 0);\n",
       "  {\n",
       "    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_s,&__pyx_n_s_t,0};\n",
       "    PyObject* values[2] = {0,0};\n",
       "    if (unlikely(__pyx_kwds)) {\n",
       "      Py_ssize_t kw_args;\n",
       "      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);\n",
       "      switch (pos_args) {\n",
       "        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);\n",
       "        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);\n",
       "        case  0: break;\n",
       "        default: goto __pyx_L5_argtuple_error;\n",
       "      }\n",
       "      kw_args = PyDict_Size(__pyx_kwds);\n",
       "      switch (pos_args) {\n",
       "        case  0:\n",
       "        if (likely((values[0] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_s)) != 0)) kw_args--;\n",
       "        else goto __pyx_L5_argtuple_error;\n",
       "        case  1:\n",
       "        if (likely((values[1] = PyDict_GetItem(__pyx_kwds, __pyx_n_s_t)) != 0)) kw_args--;\n",
       "        else {\n",
       "          __Pyx_RaiseArgtupleInvalid(\"cy_levenshtein\", 1, 2, 2, 1); __PYX_ERR(0, 1, __pyx_L3_error)\n",
       "        }\n",
       "      }\n",
       "      if (unlikely(kw_args > 0)) {\n",
       "        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, \"cy_levenshtein\") < 0)) __PYX_ERR(0, 1, __pyx_L3_error)\n",
       "      }\n",
       "    } else if (PyTuple_GET_SIZE(__pyx_args) != 2) {\n",
       "      goto __pyx_L5_argtuple_error;\n",
       "    } else {\n",
       "      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);\n",
       "      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);\n",
       "    }\n",
       "    __pyx_v_s = values[0];\n",
       "    __pyx_v_t = values[1];\n",
       "  }\n",
       "  goto __pyx_L4_argument_unpacking_done;\n",
       "  __pyx_L5_argtuple_error:;\n",
       "  __Pyx_RaiseArgtupleInvalid(\"cy_levenshtein\", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 1, __pyx_L3_error)\n",
       "  __pyx_L3_error:;\n",
       "  __Pyx_AddTraceback(\"_cython_magic_09178397f426d86b92ac0473b4709898.cy_levenshtein\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return NULL;\n",
       "  __pyx_L4_argument_unpacking_done:;\n",
       "  __pyx_r = __pyx_pf_46_cython_magic_09178397f426d86b92ac0473b4709898_cy_levenshtein(__pyx_self, __pyx_v_s, __pyx_v_t);\n",
       "\n",
       "  /* function exit code */\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "\n",
       "static PyObject *__pyx_pf_46_cython_magic_09178397f426d86b92ac0473b4709898_cy_levenshtein(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_s, PyObject *__pyx_v_t) {\n",
       "  PyObject *__pyx_r = NULL;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cy_levenshtein\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L1_error:;\n",
       "  __Pyx_XDECREF(__pyx_t_5);\n",
       "  __Pyx_AddTraceback(\"_cython_magic_09178397f426d86b92ac0473b4709898.cy_levenshtein\", __pyx_clineno, __pyx_lineno, __pyx_filename);\n",
       "  __pyx_r = NULL;\n",
       "  __pyx_L0:;\n",
       "  __Pyx_XGIVEREF(__pyx_r);\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "/* … */\n",
       "  __pyx_tuple_ = PyTuple_Pack(2, __pyx_n_s_s, __pyx_n_s_t); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_tuple_);\n",
       "  __Pyx_GIVEREF(__pyx_tuple_);\n",
       "/* … */\n",
       "  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_09178397f426d86b92ac0473b4709898_1cy_levenshtein, NULL, __pyx_n_s_cython_magic_09178397f426d86b92); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_1);\n",
       "  if (PyDict_SetItem(__pyx_d, __pyx_n_s_cy_levenshtein, __pyx_t_1) < 0) __PYX_ERR(0, 1, __pyx_L1_error)\n",
       "  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;\n",
       "
+02:     return lev(s, len(s), t, len(t))
\n", "
  __Pyx_XDECREF(__pyx_r);\n",
       "  __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_s); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __pyx_t_2 = PyObject_Length(__pyx_v_s); if (unlikely(__pyx_t_2 == -1)) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __pyx_t_3 = __Pyx_PyObject_AsString(__pyx_v_t); if (unlikely((!__pyx_t_3) && PyErr_Occurred())) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __pyx_t_4 = PyObject_Length(__pyx_v_t); if (unlikely(__pyx_t_4 == -1)) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __pyx_t_5 = __Pyx_PyInt_From_int(__pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_lev(__pyx_t_1, __pyx_t_2, __pyx_t_3, __pyx_t_4)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 2, __pyx_L1_error)\n",
       "  __Pyx_GOTREF(__pyx_t_5);\n",
       "  __pyx_r = __pyx_t_5;\n",
       "  __pyx_t_5 = 0;\n",
       "  goto __pyx_L0;\n",
       "
 03: 
\n", "
+04: cdef int lev(char *s, int len_s, char *t, int len_t):
\n", "
static int __pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_lev(char *__pyx_v_s, int __pyx_v_len_s, char *__pyx_v_t, int __pyx_v_len_t) {\n",
       "  int __pyx_v_lev_s;\n",
       "  int __pyx_v_lev_t;\n",
       "  int __pyx_v_lev_b;\n",
       "  int __pyx_r;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"lev\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L0:;\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "
+05:     if len_s == 0 or len_t == 0:
\n", "
  __pyx_t_2 = ((__pyx_v_len_s == 0) != 0);\n",
       "  if (!__pyx_t_2) {\n",
       "  } else {\n",
       "    __pyx_t_1 = __pyx_t_2;\n",
       "    goto __pyx_L4_bool_binop_done;\n",
       "  }\n",
       "  __pyx_t_2 = ((__pyx_v_len_t == 0) != 0);\n",
       "  __pyx_t_1 = __pyx_t_2;\n",
       "  __pyx_L4_bool_binop_done:;\n",
       "  if (__pyx_t_1) {\n",
       "/* … */\n",
       "  }\n",
       "
+06:         return len_s or len_t
\n", "
    if (!__pyx_v_len_s) {\n",
       "    } else {\n",
       "      __pyx_t_3 = __pyx_v_len_s;\n",
       "      goto __pyx_L6_bool_binop_done;\n",
       "    }\n",
       "    __pyx_t_3 = __pyx_v_len_t;\n",
       "    __pyx_L6_bool_binop_done:;\n",
       "    __pyx_r = __pyx_t_3;\n",
       "    goto __pyx_L0;\n",
       "
 07:     cdef:
\n", "
+08:         int lev_s = lev(s, len_s-1, t, len_t  ) + 1
\n", "
  __pyx_v_lev_s = (__pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_lev(__pyx_v_s, (__pyx_v_len_s - 1), __pyx_v_t, __pyx_v_len_t) + 1);\n",
       "
+09:         int lev_t = lev(s, len_s, t, len_t-1) + 1
\n", "
  __pyx_v_lev_t = (__pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_lev(__pyx_v_s, __pyx_v_len_s, __pyx_v_t, (__pyx_v_len_t - 1)) + 1);\n",
       "
+10:         int lev_b = lev(s, len_s-1, t, len_t-1) + cost(s, len_s, t, len_t)
\n", "
  __pyx_v_lev_b = (__pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_lev(__pyx_v_s, (__pyx_v_len_s - 1), __pyx_v_t, (__pyx_v_len_t - 1)) + __pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_cost(__pyx_v_s, __pyx_v_len_s, __pyx_v_t, __pyx_v_len_t));\n",
       "
+11:     if lev_s < lev_t and lev_s < lev_b:
\n", "
  __pyx_t_2 = ((__pyx_v_lev_s < __pyx_v_lev_t) != 0);\n",
       "  if (__pyx_t_2) {\n",
       "  } else {\n",
       "    __pyx_t_1 = __pyx_t_2;\n",
       "    goto __pyx_L9_bool_binop_done;\n",
       "  }\n",
       "  __pyx_t_2 = ((__pyx_v_lev_s < __pyx_v_lev_b) != 0);\n",
       "  __pyx_t_1 = __pyx_t_2;\n",
       "  __pyx_L9_bool_binop_done:;\n",
       "  if (__pyx_t_1) {\n",
       "/* … */\n",
       "  }\n",
       "
+12:         return lev_s
\n", "
    __pyx_r = __pyx_v_lev_s;\n",
       "    goto __pyx_L0;\n",
       "
+13:     elif lev_t < lev_s and lev_t < lev_b:
\n", "
  __pyx_t_2 = ((__pyx_v_lev_t < __pyx_v_lev_s) != 0);\n",
       "  if (__pyx_t_2) {\n",
       "  } else {\n",
       "    __pyx_t_1 = __pyx_t_2;\n",
       "    goto __pyx_L11_bool_binop_done;\n",
       "  }\n",
       "  __pyx_t_2 = ((__pyx_v_lev_t < __pyx_v_lev_b) != 0);\n",
       "  __pyx_t_1 = __pyx_t_2;\n",
       "  __pyx_L11_bool_binop_done:;\n",
       "  if (__pyx_t_1) {\n",
       "/* … */\n",
       "  }\n",
       "
+14:         return lev_t
\n", "
    __pyx_r = __pyx_v_lev_t;\n",
       "    goto __pyx_L0;\n",
       "
 15:     else:
\n", "
+16:         return lev_b
\n", "
  /*else*/ {\n",
       "    __pyx_r = __pyx_v_lev_b;\n",
       "    goto __pyx_L0;\n",
       "  }\n",
       "
 17: 
\n", "
+18: cdef int cost(char *s, int len_s, char *t, int len_t):
\n", "
static int __pyx_f_46_cython_magic_09178397f426d86b92ac0473b4709898_cost(char *__pyx_v_s, int __pyx_v_len_s, char *__pyx_v_t, int __pyx_v_len_t) {\n",
       "  int __pyx_r;\n",
       "  __Pyx_RefNannyDeclarations\n",
       "  __Pyx_RefNannySetupContext(\"cost\", 0);\n",
       "/* … */\n",
       "  /* function exit code */\n",
       "  __pyx_L0:;\n",
       "  __Pyx_RefNannyFinishContext();\n",
       "  return __pyx_r;\n",
       "}\n",
       "
+19:     return s[len_s-1] != t[len_t-1]
\n", "
  __pyx_r = ((__pyx_v_s[(__pyx_v_len_s - 1)]) != (__pyx_v_t[(__pyx_v_len_t - 1)]));\n",
       "  goto __pyx_L0;\n",
       "
" ], "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%%cython -a\n", "def cy_levenshtein(s, t):\n", " return lev(s, len(s), t, len(t))\n", "\n", "cdef int lev(char *s, int len_s, char *t, int len_t):\n", " if len_s == 0 or len_t == 0:\n", " return len_s or len_t\n", " cdef:\n", " int lev_s = lev(s, len_s-1, t, len_t ) + 1\n", " int lev_t = lev(s, len_s, t, len_t-1) + 1\n", " int lev_b = lev(s, len_s-1, t, len_t-1) + cost(s, len_s, t, len_t)\n", " if lev_s < lev_t and lev_s < lev_b:\n", " return lev_s\n", " elif lev_t < lev_s and lev_t < lev_b:\n", " return lev_t\n", " else:\n", " return lev_b\n", "\n", "cdef int cost(char *s, int len_s, char *t, int len_t):\n", " return s[len_s-1] != t[len_t-1]" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "143 ms ± 498 µs per loop (mean ± std. dev. of 3 runs, 50 loops each)\n", "1.28 ms ± 95 µs per loop (mean ± std. dev. of 3 runs, 50 loops each)\n" ] } ], "source": [ "a = b'abcdefgh'\n", "b = b'adqdfyhi'\n", "%timeit -n 50 -r 3 py_levenshtein(a, b)\n", "%timeit -n 50 -r 3 cy_levenshtein(a, b)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Wrapping C code\n", "\n", "**`levenshtein.h`**\n", "```C\n", "#ifndef LEVENSHTEIN_H__\n", "#define LEVENSHTEIN_H__\n", "\n", "int levenshtein_dist(const char *s, const char *t);\n", "\n", "#endif\n", "```\n", "\n", "**`levenshtein.c`**\n", "```C\n", "#include \"levenshtein.h\"\n", "#include \n", "#include \n", "#define MIN2(a, b) ((a) < (b) ? (a) : (b))\n", "#define MIN3(a, b, c) (MIN2((c), MIN2((a), (b))))\n", "\n", "int levenshtein_dist(const char *s, const char *t) {\n", " int i=0, j=0;\n", " int m=strlen(s), n=strlen(t);\n", " int *d = (int*)malloc((m+1) * (n+1) * sizeof(int));\n", " if (d == NULL) goto fail;\n", "\n", " for (i=1; i