From f42884bb7bd1528b0d025548b0b044198a339bb9 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 09:00:31 +0100 Subject: [PATCH 01/23] add a typed cpdef function --- src/cyFibo.pyx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cyFibo.pyx b/src/cyFibo.pyx index 1ca14c7..888515d 100755 --- a/src/cyFibo.pyx +++ b/src/cyFibo.pyx @@ -33,3 +33,10 @@ cpdef fib_cpdef(int n): if n < 2: return n return fib_cpdef(n-2) + fib_cpdef(n-1) + + +cpdef int fib_int_cpdef(int n): + """Typed cpdef.""" + if n < 2: + return n + return fib_int_cpdef(n-2) + fib_int_cpdef(n-1) From d137a14f6e96b3132a60b893055532674a38260d Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:18:35 +0100 Subject: [PATCH 02/23] add a testcase for the fibonacci functions --- src/test_fibo.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/test_fibo.py diff --git a/src/test_fibo.py b/src/test_fibo.py new file mode 100644 index 0000000..4bde176 --- /dev/null +++ b/src/test_fibo.py @@ -0,0 +1,39 @@ +import unittest + +from Fibo import fib, fib_cached +from cFibo import fib as cfib +from cyFibo import fib as cyfib +from cyFibo import fib_cdef, fib_int, fib_cpdef, fib_int_cpdef + + +class TestFibo(unittest.TestCase): + + def _check_fibonacci(self, function): + expected = [ + 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144] + result = [function(i) for i in range(13)] + self.assertEqual(expected, result) + + def test_fib(self): + self._check_fibonacci(fib) + + def test_cfib(self): + self._check_fibonacci(cfib) + + def test_cyfib(self): + self._check_fibonacci(cyfib) + + def test_fib_cdef(self): + self._check_fibonacci(fib_cdef) + + def test_fib_cpdef(self): + self._check_fibonacci(fib_cpdef) + + def test_fib_int_cpdef(self): + self._check_fibonacci(fib_int_cpdef) + + def test_fib_int(self): + self._check_fibonacci(fib_int) + + def test_fib_cached(self): + self._check_fibonacci(fib_cached) From ac67f70820d8270b4e11430cfd7562fe33821dbb Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:21:06 +0100 Subject: [PATCH 03/23] fix: support both int and long --- src/cFiboExt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cFiboExt.c b/src/cFiboExt.c index afce1b7..c94ebf1 100644 --- a/src/cFiboExt.c +++ b/src/cFiboExt.c @@ -13,7 +13,7 @@ static PyObject *python_fibonacci(PyObject *module, PyObject *arg) { PyObject *ret = NULL; assert(arg); Py_INCREF(arg); - if (! PyLong_CheckExact(arg)) { + if (!PyLong_Check(arg) & !PyInt_Check(arg)) { PyErr_SetString(PyExc_ValueError, "Argument is not an integer."); goto except; } From 62d82fe87783bb80a1b45771061c5f3e70676e02 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:21:55 +0100 Subject: [PATCH 04/23] use long for consistancy --- src/cyFibo.pyx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/cyFibo.pyx b/src/cyFibo.pyx index 888515d..8681b53 100755 --- a/src/cyFibo.pyx +++ b/src/cyFibo.pyx @@ -11,31 +11,32 @@ def fib(n): return fib(n-2) + fib(n-1) -def fib_int(int n): +def fib_int(long n): """Vanilla Python with type specification.""" if n < 2: return n return fib_int(n-2) + fib_int(n-1) -def fib_cdef(int n): +def fib_cdef(long n): """Call a cdef.""" return fib_in_c(n) -cdef int fib_in_c(int n): +cdef long fib_in_c(long n): if n < 2: return n return fib_in_c(n-2) + fib_in_c(n-1) -cpdef fib_cpdef(int n): + +cpdef fib_cpdef(long n): """Basic cpdef.""" if n < 2: return n return fib_cpdef(n-2) + fib_cpdef(n-1) -cpdef int fib_int_cpdef(int n): +cpdef long fib_int_cpdef(long n): """Typed cpdef.""" if n < 2: return n From 419c13da656895ee9dd9a49735602cfa5ddeac1b Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:22:22 +0100 Subject: [PATCH 05/23] add cpdef_int to timeit --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 3839a14..a110d34 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ script: - python -m timeit -s "import cyFibo" "cyFibo.fib_int(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib_cdef(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib_cpdef(30)" + - python -m timeit -s "import cyFibo" "cyFibo.fib_int_cpdef(30)" - python -m timeit -s "import Fibo" "Fibo.fib_cached(30)" - python -m timeit -s "import cyStdDev; import numpy as np; a = np.arange(1e6)" "cyStdDev.cStdDev(a)" - python FiboTimeit.py From 25988a26b85be00b8011b1b985e970e74dba73e4 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:23:54 +0100 Subject: [PATCH 06/23] add the handwraped extension into the timeit commands --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index a110d34..4cc5b98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ script: - python -m timeit -s "import cyFibo" "cyFibo.fib_cdef(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib_cpdef(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib_int_cpdef(30)" + - python -m timeit -s "import cFibo" "cFibo.fib(30)" - python -m timeit -s "import Fibo" "Fibo.fib_cached(30)" - python -m timeit -s "import cyStdDev; import numpy as np; a = np.arange(1e6)" "cyStdDev.cStdDev(a)" - python FiboTimeit.py From 250d060af1816739a83b95f3179c58444d36e1b1 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:25:16 +0100 Subject: [PATCH 07/23] run the tests on travis-ci --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4cc5b98..7eafdbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ install: - cd src - python setup.py build_ext --inplace script: + - python -m unittest discover ./src - python -m timeit -s "import Fibo" "Fibo.fib(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib_int(30)" From 45ae2075d283aa3a3760b2c9f9b84bd7a130c84a Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 10:35:01 +0100 Subject: [PATCH 08/23] fix test command --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7eafdbc..69d9f18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: - cd src - python setup.py build_ext --inplace script: - - python -m unittest discover ./src + - python -m unittest discover - python -m timeit -s "import Fibo" "Fibo.fib(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib(30)" - python -m timeit -s "import cyFibo" "cyFibo.fib_int(30)" From f9cebb99e49bdad68cbf8f7441003e4bcf0555cc Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 13:32:26 +0100 Subject: [PATCH 09/23] better support for python2 and python 3 checks --- src/cFiboExt.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cFiboExt.c b/src/cFiboExt.c index c94ebf1..c6ff411 100644 --- a/src/cFiboExt.c +++ b/src/cFiboExt.c @@ -13,7 +13,11 @@ static PyObject *python_fibonacci(PyObject *module, PyObject *arg) { PyObject *ret = NULL; assert(arg); Py_INCREF(arg); +#if PY_MAJOR_VERSION >= 3 + if (!PyLong_Check(arg)) { +#else if (!PyLong_Check(arg) & !PyInt_Check(arg)) { +#endif PyErr_SetString(PyExc_ValueError, "Argument is not an integer."); goto except; } From 81459d6e0e9ecd7ff25770239b059fd96996ff0d Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 14:08:03 +0100 Subject: [PATCH 10/23] use a bench script --- .travis.yml | 9 +-------- src/fibo_bench.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 src/fibo_bench.py diff --git a/.travis.yml b/.travis.yml index 69d9f18..409ddf5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,6 @@ install: - python setup.py build_ext --inplace script: - python -m unittest discover - - python -m timeit -s "import Fibo" "Fibo.fib(30)" - - python -m timeit -s "import cyFibo" "cyFibo.fib(30)" - - python -m timeit -s "import cyFibo" "cyFibo.fib_int(30)" - - python -m timeit -s "import cyFibo" "cyFibo.fib_cdef(30)" - - python -m timeit -s "import cyFibo" "cyFibo.fib_cpdef(30)" - - python -m timeit -s "import cyFibo" "cyFibo.fib_int_cpdef(30)" - - python -m timeit -s "import cFibo" "cFibo.fib(30)" - - python -m timeit -s "import Fibo" "Fibo.fib_cached(30)" + - python fibo_bench.py - python -m timeit -s "import cyStdDev; import numpy as np; a = np.arange(1e6)" "cyStdDev.cStdDev(a)" - python FiboTimeit.py diff --git a/src/fibo_bench.py b/src/fibo_bench.py new file mode 100644 index 0000000..4bdf7b7 --- /dev/null +++ b/src/fibo_bench.py @@ -0,0 +1,27 @@ +from timeit import main as timeit +from collections import namedtuple, OrderedDict + +Bench = namedtuple('Bench', ['setup', 'call']) + + +methods = OrderedDict([ + ('Python', Bench("import Fibo", "Fibo.fib(30)")), + ('Cython naive', Bench("import cyFibo", "cyFibo.fib(30)")), + ('Cython typed', Bench("import cyFibo", "cyFibo.fib_int(30)")), + ('Cython cdef', Bench("import cyFibo", "cyFibo.fib_cdef(30)")), + ('Cython cpdef', Bench("import cyFibo", "cyFibo.fib_cpdef(30)")), + ('Cython typed cpdef', + Bench("import cyFibo", "cyFibo.fib_int_cpdef(30)")), + ('Wrapped', Bench("import cFibo", "cFibo.fib(30)")), + ('Python alt', Bench("import Fibo", "Fibo.fib_cached(30)"))]) + + +def main(): + for method in methods: + print(method) + timeit([ + methods[method].setup, + methods[method].call]) + +if __name__ == '__main__': + main() From 5f6b8f5d39da9840acb92d29bfdff29b9615745e Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 15:11:38 +0100 Subject: [PATCH 11/23] minor cleanup --- src/cFiboExt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cFiboExt.c b/src/cFiboExt.c index c6ff411..2bb44f7 100644 --- a/src/cFiboExt.c +++ b/src/cFiboExt.c @@ -24,7 +24,7 @@ static PyObject *python_fibonacci(PyObject *module, PyObject *arg) { long ordinal = PyLong_AsLong(arg); long result = c_fibonacci(ordinal); ret = PyLong_FromLong(result); - assert(! PyErr_Occurred()); + assert(!PyErr_Occurred()); assert(ret); goto finally; except: From 009c1a60e167846bfad9aa2ac473129fcd01b4cb Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Fri, 1 May 2015 16:35:18 +0100 Subject: [PATCH 12/23] add batch file for documentation build --- doc/make.bat | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 doc/make.bat diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..68a758d --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pikos.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pikos.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end From dcaf0469ad05639ff3382d4b133066cf8a32a93d Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 7 May 2015 19:36:04 +0100 Subject: [PATCH 13/23] wip commit --- src/fibo_bench.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/fibo_bench.py b/src/fibo_bench.py index 4bdf7b7..51a90d1 100644 --- a/src/fibo_bench.py +++ b/src/fibo_bench.py @@ -5,15 +5,15 @@ methods = OrderedDict([ - ('Python', Bench("import Fibo", "Fibo.fib(30)")), - ('Cython naive', Bench("import cyFibo", "cyFibo.fib(30)")), - ('Cython typed', Bench("import cyFibo", "cyFibo.fib_int(30)")), - ('Cython cdef', Bench("import cyFibo", "cyFibo.fib_cdef(30)")), - ('Cython cpdef', Bench("import cyFibo", "cyFibo.fib_cpdef(30)")), + ('Python', Bench("import Fibo", "Fibo.fib({})")), + ('Cython naive', Bench("import cyFibo", "cyFibo.fib({})")), + ('Cython typed', Bench("import cyFibo", "cyFibo.fib_int({})")), + ('Cython cdef', Bench("import cyFibo", "cyFibo.fib_cdef({})")), + ('Cython cpdef', Bench("import cyFibo", "cyFibo.fib_cpdef({})")), ('Cython typed cpdef', - Bench("import cyFibo", "cyFibo.fib_int_cpdef(30)")), - ('Wrapped', Bench("import cFibo", "cFibo.fib(30)")), - ('Python alt', Bench("import Fibo", "Fibo.fib_cached(30)"))]) + Bench("import cyFibo", "cyFibo.fib_int_cpdef({})")), + ('Wrapped C', Bench("import cFibo", "cFibo.fib({})")), + ('Python alt', Bench("import Fibo", "Fibo.fib_cached({})"))]) def main(): @@ -22,6 +22,12 @@ def main(): timeit([ methods[method].setup, methods[method].call]) + for name in methods: + method = methods[name] + timer = Timer(method.call.format(30), setup=method.setup) + print(u"{}: {:g} ms".format( + name, min(timer.repeat(3, 100)) * 1e3 / 100)) + if __name__ == '__main__': main() From bbff42e295d0dff9da58708f61f246ed35267091 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 04:18:52 +0100 Subject: [PATCH 14/23] use the console_timeit --- src/fibo_bench.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/fibo_bench.py b/src/fibo_bench.py index 51a90d1..6f7cd05 100644 --- a/src/fibo_bench.py +++ b/src/fibo_bench.py @@ -1,4 +1,5 @@ -from timeit import main as timeit +from timeit import Timer +from timeit import main as console_timeit from collections import namedtuple, OrderedDict Bench = namedtuple('Bench', ['setup', 'call']) @@ -19,15 +20,9 @@ def main(): for method in methods: print(method) - timeit([ + console_timeit([ methods[method].setup, - methods[method].call]) - for name in methods: - method = methods[name] - timer = Timer(method.call.format(30), setup=method.setup) - print(u"{}: {:g} ms".format( - name, min(timer.repeat(3, 100)) * 1e3 / 100)) - + methods[method].call.format(30)]) if __name__ == '__main__': main() From 8f5bdfd09e7419abe7164505c0a9927c99f8f2dd Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:43:08 +0100 Subject: [PATCH 15/23] add code sample for the typed cpdef --- doc/source/fibo_speed.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/fibo_speed.rst b/doc/source/fibo_speed.rst index b16205b..aaa3be1 100644 --- a/doc/source/fibo_speed.rst +++ b/doc/source/fibo_speed.rst @@ -44,13 +44,20 @@ In Cython calling C generated code. Here we use a ``def`` to call a ``cdef`` tha return n return fib_in_c(n-2) + fib_in_c(n-1) -Now a recursive ``cpdef``:: +Now a recursive ``cpdef`` returning a python object:: cpdef fib_cpdef(int n): if n < 2: return n return fib_cpdef(n-2) + fib_cpdef(n-1) +A recursive ``cpdef`` returning an int:: + + cpdef int fib_typed_cpdef(int n): + if n < 2: + return n + return fib_typed_cpdef(n-2) + fib_typed_cpdef(n-1) + Finally a C extension. We expect this to be the fastest way of computing the result given the algorithm we have chosen: .. code-block:: c From 67195a103451930c9691ba26bb96c588c5d52489 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:43:37 +0100 Subject: [PATCH 16/23] add small script to create the fib comparison plot --- src/makeplot.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/makeplot.py diff --git a/src/makeplot.py b/src/makeplot.py new file mode 100644 index 0000000..cbc5eee --- /dev/null +++ b/src/makeplot.py @@ -0,0 +1,17 @@ +from matplotlib import pylab +from matplotlib.ticker import ScalarFormatter + +import numpy +import seaborn +seaborn.set(style="white", context="talk") +time = numpy.array([571, 229, 165, 7.31, 39.6, 5.61, 6.75]) +labels= numpy.array([ + 'Python', 'def() naive', 'def() typed', 'cdef()', 'cpdef()', 'cpdef typed', 'C']) +hlines=numpy.arange(1, 10) +axes = seaborn.barplot(y=time, x=labels, x_order=labels) +axes.yaxis.label.set_text("Time (ms)") +axes.yaxis.grid(color='black', which='both') +axes.set_yscale('log') +axes.set_ylim(1, 1000) +axes.yaxis.set_major_formatter(ScalarFormatter()) +pylab.show() From 6e347ab92072d83058ab7641d26abee15d0c27c2 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:44:02 +0100 Subject: [PATCH 17/23] update info on running the bench marks --- doc/source/fibo_speed.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/doc/source/fibo_speed.rst b/doc/source/fibo_speed.rst index aaa3be1..4c9e513 100644 --- a/doc/source/fibo_speed.rst +++ b/doc/source/fibo_speed.rst @@ -144,19 +144,13 @@ Finally a C extension. We expect this to be the fastest way of computing the res Benchmarks ------------------- -First a correctness check on Fibonacci(30):: +First a correctness check on the methods:: - $ python3 -c "import Fibo, cyFibo, cFibo; print(Fibo.fib(30) == cyFibo.fib(30) == cyFibo.fib_int(30) == cyFibo.fib_cdef(30) == cyFibo.fib_cpdef(30) == cFibo.fib(30))" - True + python -m unittest discover Now time these algorithms on Fibonacci(30) thus:: - $ python3 -m timeit -s "import Fibo" "Fibo.fib(30)" - $ python3 -m timeit -s "import cyFibo" "cyFibo.fib(30)" - $ python3 -m timeit -s "import cyFibo" "cyFibo.fib_int(30)" - $ python3 -m timeit -s "import cyFibo" "cyFibo.fib_cdef(30)" - $ python3 -m timeit -s "import cyFibo" "cyFibo.fib_cpdef(30)" - $ python3 -m timeit -s "import cFibo" "cFibo.fib(30)" + python fibo_bench.py Gives: From 8f3c364424d309cb146a498e299029935583a9f5 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:44:27 +0100 Subject: [PATCH 18/23] use a literalinclude directive --- doc/source/fibo_speed.rst | 82 +-------------------------------------- 1 file changed, 2 insertions(+), 80 deletions(-) diff --git a/doc/source/fibo_speed.rst b/doc/source/fibo_speed.rst index 4c9e513..9301a45 100644 --- a/doc/source/fibo_speed.rst +++ b/doc/source/fibo_speed.rst @@ -60,86 +60,8 @@ A recursive ``cpdef`` returning an int:: Finally a C extension. We expect this to be the fastest way of computing the result given the algorithm we have chosen: -.. code-block:: c - - #include "Python.h" - - /* This is the function that actually computes the Fibonacci value. */ - static long c_fibonacci(long ord) { - if (ord < 2) { - return ord; - } - return c_fibonacci(ord - 2) + c_fibonacci(ord -1); - } - - /* The Python interface to the C code. */ - static PyObject *python_fibonacci(PyObject *module, PyObject *arg) { - PyObject *ret = NULL; - assert(arg); - Py_INCREF(arg); - if (! PyLong_CheckExact(arg)) { - PyErr_SetString(PyExc_ValueError, "Argument is not an integer."); - goto except; - } - long ordinal = PyLong_AsLong(arg); - long result = c_fibonacci(ordinal); - ret = PyLong_FromLong(result); - assert(! PyErr_Occurred()); - assert(ret); - goto finally; - except: - Py_XDECREF(ret); - ret = NULL; - finally: - Py_DECREF(arg); - return ret; - } - - /********* The rest is standard Python Extension code ***********/ - - - static PyMethodDef cFiboExt_methods[] = { - {"fib", python_fibonacci, METH_O, "Fibonacci value."}, - {NULL, NULL, 0, NULL} /* sentinel */ - }; - - - #if PY_MAJOR_VERSION >= 3 - - /********* PYTHON 3 Boilerplate ***********/ - - PyDoc_STRVAR(module_doc, "Fibonacci in C."); - - static struct PyModuleDef cFiboExt = { - PyModuleDef_HEAD_INIT, - "cFibo", - module_doc, - -1, - cFiboExt_methods, - NULL, - NULL, - NULL, - NULL - }; - - PyMODINIT_FUNC - PyInit_cFibo(void) - { - return PyModule_Create(&cFiboExt); - } - - #else - - /********* PYTHON 2 Boilerplate ***********/ - - - PyMODINIT_FUNC - initcFibo(void) - { - (void) Py_InitModule("cFibo", cFiboExt_methods); - } - - #endif +.. literalinclude:: ../../src/cFiboExt.c + :language: c Benchmarks ------------------- From 4ef787b75186316d5b0e9fa35582c6c38289593a Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:44:49 +0100 Subject: [PATCH 19/23] update table based on recent runs in travis-ci --- doc/source/fibo_speed.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/source/fibo_speed.rst b/doc/source/fibo_speed.rst index 9301a45..269154f 100644 --- a/doc/source/fibo_speed.rst +++ b/doc/source/fibo_speed.rst @@ -76,16 +76,17 @@ Now time these algorithms on Fibonacci(30) thus:: Gives: -======== =========================== ============= ================= -Language Function call Time (ms) Speed, Python = 1 -======== =========================== ============= ================= -Python ``Fibo.fib(30)`` 390 x 1 -Cython ``cyFibo.fib(30)`` 215 x 1.8 -Cython ``cyFibo.fib_int(30)`` 154 x 2.5 -Cython ``cyFibo.fib_cdef(30)`` 5.38 x72 -Cython ``cyFibo.fib_cpdef(30)`` 32.5 x12 -C ``cFibo.fib(30)`` 5.31 x73 -======== =========================== ============= ================= +======== ============================ ============= ================= +Language Function call Time (ms) Speed, Python = 1 +======== ============================ ============= ================= +Python ``Fibo.fib(30)`` 571 x1 +Cython ``cyFibo.fib(30)`` 229 x2.5 +Cython ``cyFibo.fib_int(30)`` 165 x3.5 +Cython ``cyFibo.fib_cdef(30)`` 7.31 x78 +Cython ``cyFibo.fib_cpdef(30)`` 39.6 x14 +Cython ``cyFibo.fib_int cpdef(30)`` 5.61 x102 +C ``cFibo.fib(30)`` 6.75 x85 +======== ============================ ============= ================= Graphically: From 15376566ead59501bfeb92e90c4bbf2b5057b896 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:45:22 +0100 Subject: [PATCH 20/23] update discussion and layout --- doc/source/fibo_speed.rst | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/doc/source/fibo_speed.rst b/doc/source/fibo_speed.rst index 269154f..ebfd436 100644 --- a/doc/source/fibo_speed.rst +++ b/doc/source/fibo_speed.rst @@ -95,15 +95,24 @@ Graphically: The conclusions that I draw from this are: * Naive Cython does speed things up, but not by much (x1.8). -* Optimised Cython is fairly effortless (in this case) and worthwhile (x2.5). +* Optimised Cython is fairly effortless (in this case) and worthwhile + (x2.5). +* ``cpdef`` gives a good improvement over ``def`` because the + recursive case exploits C functions. * ``cdef`` is really valuable (x72). -* ``cpdef`` gives a good improvement over ``def`` because the recursive case exploits C functions. -* Cython's ``cdef`` is insignificantly different from the more complicated C extension that is our best attempt. +* Cython's ``cdef`` is insignificantly different from the more + complicated C extension that is our best attempt. +* ``typed cpdef`` gives the best of two worlds and (in our example) it + is even faster than the hand wrapping of the C function. The Importance of the Algorithm ------------------------------------- -So far we have looked at pushing code into Cython/C to get a performance gain however there is a glaring error in our code. The algorithm we have been using is **very** inefficient. Here is different algorithm, in pure Python, that will beat all of those above by a huge margin [#]_: +So far we have looked at pushing code into Cython/C to get a +performance gain however there is a glaring error in our code. The +algorithm we have been using is **very** inefficient. Here is +different algorithm, in pure Python, that will beat all of those above +by a huge margin [#]_: .. code-block:: python @@ -131,12 +140,16 @@ Or, graphically: .. image:: images/CacheComparison.png -In fact our new algorithm is far, far better than that. Here is the O(N) behaviour where N is the Fibonacci ordinal: +In fact our new algorithm is far, far better than that. Here is the +O(N) behaviour where N is the Fibonacci ordinal: .. image:: images/CacheON.png -Hammering a bad algorithm with a fast language is worse than using a good algorithm and a slow language. +Hammering a bad algorithm with a fast language is worse than using a +good algorithm and a slow language. .. rubric:: Footnotes -.. [#] If you are using Python3 you can use the ``functools.lru_cache`` decorator that gives you more control over cache behaviour. +.. [#] If you are using Python3 you can use the + ``functools.lru_cache`` decorator that gives you more control + over cache behaviour. From 89a57e05d6a60c9d809498778c3a9b90680498fa Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 06:45:34 +0100 Subject: [PATCH 21/23] update comparison graph --- doc/source/images/FibonacciComparison.png | Bin 20573 -> 26848 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/source/images/FibonacciComparison.png b/doc/source/images/FibonacciComparison.png index b39b4895f8fb1f7ea4aea3a4b6e9a45f220d38af..3221e2157e857dec943577248ac3390f56ee2ed6 100644 GIT binary patch literal 26848 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%0AQWp8$L7*A)a>))_An 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 Date: Sat, 9 May 2015 06:56:39 +0100 Subject: [PATCH 22/23] Update makeplot.py --- src/makeplot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/makeplot.py b/src/makeplot.py index cbc5eee..4132ec6 100644 --- a/src/makeplot.py +++ b/src/makeplot.py @@ -7,7 +7,6 @@ time = numpy.array([571, 229, 165, 7.31, 39.6, 5.61, 6.75]) labels= numpy.array([ 'Python', 'def() naive', 'def() typed', 'cdef()', 'cpdef()', 'cpdef typed', 'C']) -hlines=numpy.arange(1, 10) axes = seaborn.barplot(y=time, x=labels, x_order=labels) axes.yaxis.label.set_text("Time (ms)") axes.yaxis.grid(color='black', which='both') From b1728b5c1c48b852ec5305fbced5231c68d2a35f Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Sat, 9 May 2015 07:06:38 +0100 Subject: [PATCH 23/23] Update fibo_speed.rst --- doc/source/fibo_speed.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/fibo_speed.rst b/doc/source/fibo_speed.rst index ebfd436..a57247b 100644 --- a/doc/source/fibo_speed.rst +++ b/doc/source/fibo_speed.rst @@ -94,12 +94,12 @@ Graphically: The conclusions that I draw from this are: -* Naive Cython does speed things up, but not by much (x1.8). +* Naive Cython does speed things up, but not by much (x2.5). * Optimised Cython is fairly effortless (in this case) and worthwhile - (x2.5). + (x3.5). * ``cpdef`` gives a good improvement over ``def`` because the recursive case exploits C functions. -* ``cdef`` is really valuable (x72). +* ``cdef`` is really valuable (x78). * Cython's ``cdef`` is insignificantly different from the more complicated C extension that is our best attempt. * ``typed cpdef`` gives the best of two worlds and (in our example) it