Commit e2941f2b authored by gabrieldemarmiesse's avatar gabrieldemarmiesse

Finished 'the first cython program'.

parent adc1aa70
{ {
"metadata": { "metadata": {
"name": "Cython Magics", "name": "Cython Magics",
"signature": "sha256:c357b93e9480d6347c6677862bf43750745cef4b30129c5bc53cb879a19d4074" "signature": "sha256:c357b93e9480d6347c6677862bf43750745cef4b30129c5bc53cb879a19d4074"
}, },
"nbformat": 3, "nbformat": 3,
"nbformat_minor": 0, "nbformat_minor": 0,
"worksheets": [ "worksheets": [
{ {
"cells": [ "cells": [
{ {
"cell_type": "heading", "cell_type": "heading",
"level": 1, "level": 1,
"metadata": {}, "metadata": {},
"source": [ "source": [
"Cython Magic Functions" "Cython Magic Functions"
] ]
}, },
{ {
"cell_type": "heading", "cell_type": "heading",
"level": 2, "level": 2,
"metadata": {}, "metadata": {},
"source": [ "source": [
"Loading the extension" "Loading the extension"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Cython has an IPython extension that contains a number of magic functions for working with Cython code. This extension can be loaded using the `%load_ext` magic as follows:" "Cython has an IPython extension that contains a number of magic functions for working with Cython code. This extension can be loaded using the `%load_ext` magic as follows:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%load_ext cython" "%load_ext cython"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"prompt_number": 1 "prompt_number": 1
}, },
{ {
"cell_type": "heading", "cell_type": "heading",
"level": 2, "level": 2,
"metadata": {}, "metadata": {},
"source": [ "source": [
"The %cython_inline magic" "The %cython_inline magic"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The `%%cython_inline` magic uses `Cython.inline` to compile a Cython expression. This allows you to enter and run a function body with Cython code. Use a bare `return` statement to return values. " "The `%%cython_inline` magic uses `Cython.inline` to compile a Cython expression. This allows you to enter and run a function body with Cython code. Use a bare `return` statement to return values. "
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"a = 10\n", "a = 10\n",
"b = 20" "b = 20"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"prompt_number": 2 "prompt_number": 2
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%%cython_inline\n", "%%cython_inline\n",
"return a+b" "return a+b"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"metadata": {}, "metadata": {},
"output_type": "pyout", "output_type": "pyout",
"prompt_number": 3, "prompt_number": 3,
"text": [ "text": [
"30" "30"
] ]
} }
], ],
"prompt_number": 3 "prompt_number": 3
}, },
{ {
"cell_type": "heading", "cell_type": "heading",
"level": 2, "level": 2,
"metadata": {}, "metadata": {},
"source": [ "source": [
"The %cython_pyximport magic" "The %cython_pyximport magic"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"The `%%cython_pyximport` magic allows you to enter arbitrary Cython code into a cell. That Cython code is written as a `.pyx` file in the current working directory and then imported using `pyximport`. You have the specify the name of the module that the Code will appear in. All symbols from the module are imported automatically by the magic function." "The `%%cython_pyximport` magic allows you to enter arbitrary Cython code into a cell. That Cython code is written as a `.pyx` file in the current working directory and then imported using `pyximport`. You have the specify the name of the module that the Code will appear in. All symbols from the module are imported automatically by the magic function."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%%cython_pyximport foo\n", "%%cython_pyximport foo\n",
"def f(x):\n", "def f(x):\n",
" return 4.0*x" " return 4.0*x"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"prompt_number": 4 "prompt_number": 4
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"f(10)" "f(10)"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"metadata": {}, "metadata": {},
"output_type": "pyout", "output_type": "pyout",
"prompt_number": 5, "prompt_number": 5,
"text": [ "text": [
"40.0" "40.0"
] ]
} }
], ],
"prompt_number": 5 "prompt_number": 5
}, },
{ {
"cell_type": "heading", "cell_type": "heading",
"level": 2, "level": 2,
"metadata": {}, "metadata": {},
"source": [ "source": [
"The %cython magic" "The %cython magic"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Probably the most important magic is the `%cython` magic. This is similar to the `%%cython_pyximport` magic, but doesn't require you to specify a module name. Instead, the `%%cython` magic uses manages everything using temporary files in the `~/.cython/magic` directory. All of the symbols in the Cython module are imported automatically by the magic.\n", "Probably the most important magic is the `%cython` magic. This is similar to the `%%cython_pyximport` magic, but doesn't require you to specify a module name. Instead, the `%%cython` magic uses manages everything using temporary files in the `~/.cython/magic` directory. All of the symbols in the Cython module are imported automatically by the magic.\n",
"\n", "\n",
"Here is a simple example of a Black-Scholes options pricing algorithm written in Cython. Please note that this example might not compile on non-POSIX systems (e.g., Windows) because of a missing `erf` symbol." "Here is a simple example of a Black-Scholes options pricing algorithm written in Cython. Please note that this example might not compile on non-POSIX systems (e.g., Windows) because of a missing `erf` symbol."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%%cython\n", "%%cython\n",
"cimport cython\n", "cimport cython\n",
"from libc.math cimport exp, sqrt, pow, log, erf\n", "from libc.math cimport exp, sqrt, pow, log, erf\n",
"\n", "\n",
"@cython.cdivision(True)\n", "@cython.cdivision(True)\n",
"cdef double std_norm_cdf_cy(double x) nogil:\n", "cdef double std_norm_cdf_cy(double x) nogil:\n",
" return 0.5*(1+erf(x/sqrt(2.0)))\n", " return 0.5*(1+erf(x/sqrt(2.0)))\n",
"\n", "\n",
"@cython.cdivision(True)\n", "@cython.cdivision(True)\n",
"def black_scholes_cy(double s, double k, double t, double v,\n", "def black_scholes_cy(double s, double k, double t, double v,\n",
" double rf, double div, double cp):\n", " double rf, double div, double cp):\n",
" \"\"\"Price an option using the Black-Scholes model.\n", " \"\"\"Price an option using the Black-Scholes model.\n",
" \n", " \n",
" s : initial stock price\n", " s : initial stock price\n",
" k : strike price\n", " k : strike price\n",
" t : expiration time\n", " t : expiration time\n",
" v : volatility\n", " v : volatility\n",
" rf : risk-free rate\n", " rf : risk-free rate\n",
" div : dividend\n", " div : dividend\n",
" cp : +1/-1 for call/put\n", " cp : +1/-1 for call/put\n",
" \"\"\"\n", " \"\"\"\n",
" cdef double d1, d2, optprice\n", " cdef double d1, d2, optprice\n",
" with nogil:\n", " with nogil:\n",
" d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))\n", " d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))\n",
" d2 = d1 - v*sqrt(t)\n", " d2 = d1 - v*sqrt(t)\n",
" optprice = cp*s*exp(-div*t)*std_norm_cdf_cy(cp*d1) - \\\n", " optprice = cp*s*exp(-div*t)*std_norm_cdf_cy(cp*d1) - \\\n",
" cp*k*exp(-rf*t)*std_norm_cdf_cy(cp*d2)\n", " cp*k*exp(-rf*t)*std_norm_cdf_cy(cp*d2)\n",
" return optprice" " return optprice"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"prompt_number": 6 "prompt_number": 6
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"black_scholes_cy(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" "black_scholes_cy(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"metadata": {}, "metadata": {},
"output_type": "pyout", "output_type": "pyout",
"prompt_number": 7, "prompt_number": 7,
"text": [ "text": [
"10.327861752731728" "10.327861752731728"
] ]
} }
], ],
"prompt_number": 7 "prompt_number": 7
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"For comparison, the same code is implemented here in pure python." "For comparison, the same code is implemented here in pure python."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"from math import exp, sqrt, pow, log, erf\n", "from math import exp, sqrt, pow, log, erf\n",
"\n", "\n",
"def std_norm_cdf_py(x):\n", "def std_norm_cdf_py(x):\n",
" return 0.5*(1+erf(x/sqrt(2.0)))\n", " return 0.5*(1+erf(x/sqrt(2.0)))\n",
"\n", "\n",
"def black_scholes_py(s, k, t, v, rf, div, cp):\n", "def black_scholes_py(s, k, t, v, rf, div, cp):\n",
" \"\"\"Price an option using the Black-Scholes model.\n", " \"\"\"Price an option using the Black-Scholes model.\n",
" \n", " \n",
" s : initial stock price\n", " s : initial stock price\n",
" k : strike price\n", " k : strike price\n",
" t : expiration time\n", " t : expiration time\n",
" v : volatility\n", " v : volatility\n",
" rf : risk-free rate\n", " rf : risk-free rate\n",
" div : dividend\n", " div : dividend\n",
" cp : +1/-1 for call/put\n", " cp : +1/-1 for call/put\n",
" \"\"\"\n", " \"\"\"\n",
" d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))\n", " d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))\n",
" d2 = d1 - v*sqrt(t)\n", " d2 = d1 - v*sqrt(t)\n",
" optprice = cp*s*exp(-div*t)*std_norm_cdf_py(cp*d1) - \\\n", " optprice = cp*s*exp(-div*t)*std_norm_cdf_py(cp*d1) - \\\n",
" cp*k*exp(-rf*t)*std_norm_cdf_py(cp*d2)\n", " cp*k*exp(-rf*t)*std_norm_cdf_py(cp*d2)\n",
" return optprice" " return optprice"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"prompt_number": 8 "prompt_number": 8
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"black_scholes_py(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" "black_scholes_py(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"metadata": {}, "metadata": {},
"output_type": "pyout", "output_type": "pyout",
"prompt_number": 9, "prompt_number": 9,
"text": [ "text": [
"10.327861752731728" "10.327861752731728"
] ]
} }
], ],
"prompt_number": 9 "prompt_number": 9
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Below we see the runtime of the two functions: the Cython version is nearly a factor of 10 faster." "Below we see the runtime of the two functions: the Cython version is nearly a factor of 10 faster."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%timeit black_scholes_cy(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" "%timeit black_scholes_cy(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"output_type": "stream", "output_type": "stream",
"stream": "stdout", "stream": "stdout",
"text": [ "text": [
"1000000 loops, best of 3: 319 ns per loop\n" "1000000 loops, best of 3: 319 ns per loop\n"
] ]
} }
], ],
"prompt_number": 10 "prompt_number": 10
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%timeit black_scholes_py(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" "%timeit black_scholes_py(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"output_type": "stream", "output_type": "stream",
"stream": "stdout", "stream": "stdout",
"text": [ "text": [
"100000 loops, best of 3: 2.28 \u00b5s per loop\n" "100000 loops, best of 3: 2.28 \u00b5s per loop\n"
] ]
} }
], ],
"prompt_number": 11 "prompt_number": 11
}, },
{ {
"cell_type": "heading", "cell_type": "heading",
"level": 2, "level": 2,
"metadata": {}, "metadata": {},
"source": [ "source": [
"External libraries" "External libraries"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"Cython allows you to specify additional libraries to be linked with your extension, you can do so with the `-l` flag (also spelled `--lib`). Note that this flag can be passed more than once to specify multiple libraries, such as `-lm -llib2 --lib lib3`. Here's a simple example of how to access the system math library:" "Cython allows you to specify additional libraries to be linked with your extension, you can do so with the `-l` flag (also spelled `--lib`). Note that this flag can be passed more than once to specify multiple libraries, such as `-lm -llib2 --lib lib3`. Here's a simple example of how to access the system math library:"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"collapsed": false, "collapsed": false,
"input": [ "input": [
"%%cython -lm\n", "%%cython -lm\n",
"from libc.math cimport sin\n", "from libc.math cimport sin\n",
"print 'sin(1)=', sin(1)" "print 'sin(1)=', sin(1)"
], ],
"language": "python", "language": "python",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"output_type": "stream", "output_type": "stream",
"stream": "stdout", "stream": "stdout",
"text": [ "text": [
"sin(1)= 0.841470984808\n" "sin(1)= 0.841470984808\n"
] ]
} }
], ],
"prompt_number": 12 "prompt_number": 12
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"You can similarly use the `-I/--include` flag to add include directories to the search path, and `-c/--compile-args` to add extra flags that are passed to Cython via the `extra_compile_args` of the distutils `Extension` class. Please see [the Cython docs on C library usage](http://docs.cython.org/src/tutorial/clibraries.html) for more details on the use of these flags." "You can similarly use the `-I/--include` flag to add include directories to the search path, and `-c/--compile-args` to add extra flags that are passed to Cython via the `extra_compile_args` of the distutils `Extension` class. Please see [the Cython docs on C library usage](http://docs.cython.org/src/tutorial/clibraries.html) for more details on the use of these flags."
] ]
} }
], ],
"metadata": {} "metadata": {}
} }
] ]
} }
# cython: infer_types=True # cython: infer_types=True
import numpy as np import numpy as np
cimport cython
# "def" can type its arguments but not have a return type. The type of the
# arguments for a "def" function is checked at run-time when entering the
# function.
# We now need to fix a datatype for our arrays. I've used the variable
# DTYPE for this, which is assigned to the usual NumPy runtime
# type info object.
# The arrays f, g and h is typed as "np.ndarray" instances. The only effect
# this has is to a) insert checks that the function arguments really are
# NumPy arrays, and b) make some attribute access like f.shape[0] much
# more efficient. (In this example this doesn't matter though.)
ctypedef fused my_type: ctypedef fused my_type:
int int
...@@ -22,15 +11,7 @@ ctypedef fused my_type: ...@@ -22,15 +11,7 @@ ctypedef fused my_type:
cpdef naive_convolve_fused_types(my_type [:,:] f, my_type [:,:] g): cpdef naive_convolve_fused_types(my_type [:,:] f, my_type [:,:] g):
if g.shape[0] % 2 != 1 or g.shape[1] % 2 != 1: if g.shape[0] % 2 != 1 or g.shape[1] % 2 != 1:
raise ValueError("Only odd dimensions on filter supported") raise ValueError("Only odd dimensions on filter supported")
# The "cdef" keyword is also used within functions to type variables. It
# can only be used at the top indentation level (there are non-trivial
# problems with allowing them in other places, though we'd love to see
# good and thought out proposals for it).
#
# For the indices, the "int" type is used. This corresponds to a C int,
# other C types (like "unsigned int") could have been used instead.
# Purists could use "Py_ssize_t" which is the proper Python type for
# array indices.
vmax = f.shape[0] vmax = f.shape[0]
wmax = f.shape[1] wmax = f.shape[1]
smax = g.shape[0] smax = g.shape[0]
...@@ -50,11 +31,6 @@ cpdef naive_convolve_fused_types(my_type [:,:] f, my_type [:,:] g): ...@@ -50,11 +31,6 @@ cpdef naive_convolve_fused_types(my_type [:,:] f, my_type [:,:] g):
h_np = np.zeros([xmax, ymax], dtype=dtype) h_np = np.zeros([xmax, ymax], dtype=dtype)
cdef my_type [:,:] h = h_np cdef my_type [:,:] h = h_np
# For the value variable, we want to use the same data type as is
# stored in the array, so we use "DTYPE_t" as defined above.
# NB! An important side-effect of this is that if "value" overflows its
# datatype size, it will simply wrap around like in C, rather than raise
# an error like in Python.
cdef my_type value cdef my_type value
for x in range(xmax): for x in range(xmax):
for y in range(ymax): for y in range(ymax):
......
# cython: infer_types=True # cython: infer_types=True
import numpy as np import numpy as np
cimport cython
# "def" can type its arguments but not have a return type. The type of the
# arguments for a "def" function is checked at run-time when entering the
# function.
# We now need to fix a datatype for our arrays. I've used the variable
# DTYPE for this, which is assigned to the usual NumPy runtime
# type info object.
DTYPE = np.intc DTYPE = np.intc
# The arrays f, g and h is typed as "np.ndarray" instances. The only effect
# this has is to a) insert checks that the function arguments really are
# NumPy arrays, and b) make some attribute access like f.shape[0] much
# more efficient. (In this example this doesn't matter though.)
@cython.boundscheck(False) @cython.boundscheck(False)
@cython.wraparound(False) @cython.wraparound(False)
def naive_convolve_infer_types(int [:,::1] f, int [:,::1] g): def naive_convolve_infer_types(int [:,::1] f, int [:,::1] g):
if g.shape[0] % 2 != 1 or g.shape[1] % 2 != 1: if g.shape[0] % 2 != 1 or g.shape[1] % 2 != 1:
raise ValueError("Only odd dimensions on filter supported") raise ValueError("Only odd dimensions on filter supported")
# The "cdef" keyword is also used within functions to type variables. It
# can only be used at the top indentation level (there are non-trivial
# problems with allowing them in other places, though we'd love to see
# good and thought out proposals for it).
#
# For the indices, the "int" type is used. This corresponds to a C int,
# other C types (like "unsigned int") could have been used instead.
# Purists could use "Py_ssize_t" which is the proper Python type for
# array indices.
vmax = f.shape[0] vmax = f.shape[0]
wmax = f.shape[1] wmax = f.shape[1]
smax = g.shape[0] smax = g.shape[0]
...@@ -34,14 +17,10 @@ def naive_convolve_infer_types(int [:,::1] f, int [:,::1] g): ...@@ -34,14 +17,10 @@ def naive_convolve_infer_types(int [:,::1] f, int [:,::1] g):
tmid = tmax // 2 tmid = tmax // 2
xmax = vmax + 2*smid xmax = vmax + 2*smid
ymax = wmax + 2*tmid ymax = wmax + 2*tmid
h_np = np.zeros([xmax, ymax], dtype=DTYPE) h_np = np.zeros([xmax, ymax], dtype=DTYPE)
cdef int [:,::1] h = h_np cdef int [:,::1] h = h_np
# For the value variable, we want to use the same data type as is
# stored in the array, so we use "DTYPE_t" as defined above.
# NB! An important side-effect of this is that if "value" overflows its
# datatype size, it will simply wrap around like in C, rather than raise
# an error like in Python.
cdef int value cdef int value
for x in range(xmax): for x in range(xmax):
for y in range(ymax): for y in range(ymax):
......
import numpy as np import numpy as np
# "def" can type its arguments but not have a return type. The type of the
# arguments for a "def" function is checked at run-time when entering the
# function.
# We now need to fix a datatype for our arrays. I've used the variable
# DTYPE for this, which is assigned to the usual NumPy runtime
# type info object.
DTYPE = np.intc DTYPE = np.intc
# The arrays f, g and h is typed as "np.ndarray" instances. The only effect
# this has is to a) insert checks that the function arguments really are
# NumPy arrays, and b) make some attribute access like f.shape[0] much
# more efficient. (In this example this doesn't matter though.)
def naive_convolve_memview(int [:,:] f, int [:,:] g): def naive_convolve_memview(int [:,:] f, int [:,:] g):
if g.shape[0] % 2 != 1 or g.shape[1] % 2 != 1: if g.shape[0] % 2 != 1 or g.shape[1] % 2 != 1:
raise ValueError("Only odd dimensions on filter supported") raise ValueError("Only odd dimensions on filter supported")
# The "cdef" keyword is also used within functions to type variables. It
# can only be used at the top indentation level (there are non-trivial # We don't need to check for the type of NumPy array here because
# problems with allowing them in other places, though we'd love to see # a check is already performed when calling the function.
# good and thought out proposals for it). cdef int x, y, s, t, v, w, s_from, s_to, t_from, t_to
#
# For the indices, the "int" type is used. This corresponds to a C int,
# other C types (like "unsigned int") could have been used instead.
# Purists could use "Py_ssize_t" which is the proper Python type for
# array indices.
cdef int vmax = f.shape[0] cdef int vmax = f.shape[0]
cdef int wmax = f.shape[1] cdef int wmax = f.shape[1]
cdef int smax = g.shape[0] cdef int smax = g.shape[0]
...@@ -31,18 +17,10 @@ def naive_convolve_memview(int [:,:] f, int [:,:] g): ...@@ -31,18 +17,10 @@ def naive_convolve_memview(int [:,:] f, int [:,:] g):
cdef int tmid = tmax // 2 cdef int tmid = tmax // 2
cdef int xmax = vmax + 2*smid cdef int xmax = vmax + 2*smid
cdef int ymax = wmax + 2*tmid cdef int ymax = wmax + 2*tmid
h_np = np.zeros([xmax, ymax], dtype=DTYPE) h_np = np.zeros([xmax, ymax], dtype=DTYPE)
cdef int [:,:] h = h_np cdef int [:,:] h = h_np
cdef int x, y, s, t, v, w
# It is very important to type ALL your variables. You do not get any
# warnings if not, only much slower code (they are implicitly typed as
# Python objects).
cdef int s_from, s_to, t_from, t_to
# For the value variable, we want to use the same data type as is
# stored in the array, so we use "DTYPE_t" as defined above.
# NB! An important side-effect of this is that if "value" overflows its
# datatype size, it will simply wrap around like in C, rather than raise
# an error like in Python.
cdef int value cdef int value
for x in range(xmax): for x in range(xmax):
for y in range(ymax): for y in range(ymax):
......
...@@ -24,6 +24,8 @@ def naive_convolve_types(f, g): ...@@ -24,6 +24,8 @@ def naive_convolve_types(f, g):
# other C types (like "unsigned int") could have been used instead. # other C types (like "unsigned int") could have been used instead.
# Purists could use "Py_ssize_t" which is the proper Python type for # Purists could use "Py_ssize_t" which is the proper Python type for
# array indices. # array indices.
cdef int x, y, s, t, v, w, s_from, s_to, t_from, t_to
cdef int vmax = f.shape[0] cdef int vmax = f.shape[0]
cdef int wmax = f.shape[1] cdef int wmax = f.shape[1]
cdef int smax = g.shape[0] cdef int smax = g.shape[0]
...@@ -33,11 +35,9 @@ def naive_convolve_types(f, g): ...@@ -33,11 +35,9 @@ def naive_convolve_types(f, g):
cdef int xmax = vmax + 2*smid cdef int xmax = vmax + 2*smid
cdef int ymax = wmax + 2*tmid cdef int ymax = wmax + 2*tmid
h = np.zeros([xmax, ymax], dtype=DTYPE) h = np.zeros([xmax, ymax], dtype=DTYPE)
cdef int x, y, s, t, v, w
# It is very important to type ALL your variables. You do not get any # It is very important to type ALL your variables. You do not get any
# warnings if not, only much slower code (they are implicitly typed as # warnings if not, only much slower code (they are implicitly typed as
# Python objects). # Python objects).
cdef int s_from, s_to, t_from, t_to
# For the value variable, we want to use the same data type as is # For the value variable, we want to use the same data type as is
# stored in the array, so we use "DTYPE_t" as defined above. # stored in the array, so we use "DTYPE_t" as defined above.
# NB! An important side-effect of this is that if "value" overflows its # NB! An important side-effect of this is that if "value" overflows its
......
...@@ -133,13 +133,13 @@ The first Cython program ...@@ -133,13 +133,13 @@ The first Cython program
The code below does 2D discrete convolution of an image with a filter (and I'm The code below does 2D discrete convolution of an image with a filter (and I'm
sure you can do better!, let it serve for demonstration purposes). It is both sure you can do better!, let it serve for demonstration purposes). It is both
valid Python and valid Cython code. I'll refer to it as both valid Python and valid Cython code. I'll refer to it as both
:file:`convolve_py.py` for the Python version and :file:`convolve1.pyx` for the :file:`convolve_py.py` for the Python version and :file:`convolve_cy.pyx` for the
Cython version -- Cython uses ".pyx" as its file suffix. Cython version -- Cython uses ".pyx" as its file suffix.
.. literalinclude:: ../../examples/userguide/convolve_py.py .. literalinclude:: ../../examples/userguide/convolve_py.py
:linenos: :linenos:
This should be compiled to produce :file:`yourmod.so` (for Linux systems). We This should be compiled to produce :file:`convolve_cy.so` (for Linux systems). We
run a Python session to test both the Python version (imported from run a Python session to test both the Python version (imported from
``.py``-file) and the compiled Cython module. ``.py``-file) and the compiled Cython module.
...@@ -153,8 +153,8 @@ run a Python session to test both the Python version (imported from ...@@ -153,8 +153,8 @@ run a Python session to test both the Python version (imported from
array([[1, 1, 1], array([[1, 1, 1],
[2, 2, 2], [2, 2, 2],
[1, 1, 1]]) [1, 1, 1]])
In [4]: import convolve1 In [4]: import convolve_cy
In [4]: convolve1.naive_convolve(np.array([[1, 1, 1]], dtype=np.int), In [4]: convolve_cy.naive_convolve(np.array([[1, 1, 1]], dtype=np.int),
... np.array([[1],[2],[1]], dtype=np.int)) ... np.array([[1],[2],[1]], dtype=np.int))
Out [4]: Out [4]:
array([[1, 1, 1], array([[1, 1, 1],
...@@ -164,13 +164,17 @@ run a Python session to test both the Python version (imported from ...@@ -164,13 +164,17 @@ run a Python session to test both the Python version (imported from
In [12]: f = np.arange(N*N, dtype=np.int).reshape((N,N)) In [12]: f = np.arange(N*N, dtype=np.int).reshape((N,N))
In [13]: g = np.arange(81, dtype=np.int).reshape((9, 9)) In [13]: g = np.arange(81, dtype=np.int).reshape((9, 9))
In [19]: %timeit -n2 -r3 convolve_py.naive_convolve(f, g) In [19]: %timeit -n2 -r3 convolve_py.naive_convolve(f, g)
2 loops, best of 3: 1.86 s per loop 422 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [20]: %timeit -n2 -r3 convolve1.naive_convolve(f, g) In [20]: %timeit -n2 -r3 convolve_cy.naive_convolve(f, g)
2 loops, best of 3: 1.41 s per loop 342 ms ± 1.39 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
There's not such a huge difference yet; because the C code still does exactly There's not such a huge difference yet; because the C code still does exactly
what the Python interpreter does (meaning, for instance, that a new object is what the Python interpreter does (meaning, for instance, that a new object is
allocated for each number used). Look at the generated html file and see what allocated for each number used). You can look at the Python interaction
and the generated C code by using `-a` when calling Cython from the command
line, `%%cython -a` when using a Jupyter Notebook, or by using
`cythonize('convolve_cy.pyx', annotate=True)` when using a `setup.py`.
Look at the generated html file and see what
is needed for even the simplest statements you get the point quickly. We need is needed for even the simplest statements you get the point quickly. We need
to give Cython more information; we need to add types. to give Cython more information; we need to add types.
...@@ -358,9 +362,16 @@ mode). ...@@ -358,9 +362,16 @@ mode).
Where to go from here? Where to go from here?
====================== ======================
* Since there is no Python interaction in the loops, it is possible with Cython
to release the GIL and use multiple cores easily. To learn how to do that,
you can see :ref:`using parallelism in Cython <parallel>`.
* If you want to learn how to make use of `BLAS <http://www.netlib.org/blas/>`_
or `LAPACK <http://www.netlib.org/lapack/>`_ with Cython, you can watch
`the presentation of Ian Henriksen at SciPy 2015
<https://www.youtube.com/watch?v=R4yB-8tB0J0&t=693s&ab_channel=Enthought>`_.
The future The future
============ ==========
These are some points to consider for further development. All points listed These are some points to consider for further development. All points listed
here has gone through a lot of thinking and planning already; still they may here has gone through a lot of thinking and planning already; still they may
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment