{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Numba 0.45.0 Release Demo\n", "=======================\n", "\n", "This notebook contains a demonstration of new features present in the 0.45.0 release of Numba. Whilst release notes are produced as part of the [`CHANGE_LOG`](https://github.com/numba/numba/blob/7e2fa9823c1fbf94091ae6024b4cbf04d914581c/CHANGE_LOG), there's nothing like seeing code in action! It should be noted that this release does not contain as much new user facing functionality as usual, a lot of necessary work was done on Numba internals instead!\n", "\n", "Included are demonstrations of:\n", "* Type inferred lists, this is to replace the \"reflected list\", [(read why this is happening here)](http://numba.pydata.org/numba-doc/latest/reference/deprecation.html#deprecation-of-reflection-for-list-and-set-types).\n", "* Caching support for `@jit(parallel=True)` functions.\n", "* Newly supported NumPy functions.\n", "* A few new miscellaneous features!\n", "\n", "First, import the necessary from Numba and NumPy..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from numba import jit, njit, config, __version__, errors, types\n", "from numba.extending import overload\n", "import numpy as np\n", "assert tuple(int(x) for x in __version__.split('.')[:2]) >= (0, 45)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Typed Lists\n", "-----------------\n", "As noted in the [previous release notebook](https://mybinder.org/v2/gh/numba/numba-examples/master?filepath=notebooks%2FNumba_044_release_demo.ipynb), Numba Version 0.44 deprecated a number of features and issued pending-deprecation notices for others. One of the deprecations with highest impact was the pending-deprecation of [reflection of `List` and `Set` types](http://numba.pydata.org/numba-doc/latest/reference/deprecation.html#deprecation-of-reflection-for-list-and-set-types), the \"typed-list\" demonstrated herein is the replacement for the reflected list.\n", "\n", "\n", "The first important thing to note about the typed-list is that it is instantiated (manually or through type inference) with a fixed single type and as a result its items must be homogeneous and of that type, this is similar to the [typed dictionary](http://numba.pydata.org/numba-doc/latest/reference/pysupported.html) added in Numba Version 0.43. The typed-list documentation can be found [here](http://numba.pydata.org/numba-doc/latest/reference/pysupported.html) and contains further notes and examples.\n", "\n", "Demonstration of this feature starts with seeing how to change some code that would be impacted by the deprecation of the \"reflected list\":" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def foo(x):\n", " x.append(10) # changes made here need \"reflecting\" back to `a` in the outer scope\n", "\n", "a = [1, 2, 3]\n", "foo(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is the same functionality but using the new typed-list:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from numba.typed import List\n", "\n", "@njit\n", "def foo(x):\n", " x.append(10)\n", "\n", "a = List() # Create a new typed-list\n", "# Add the content to the typed-list, the list type is inferred from the items added\n", "[a.append(x) for x in [1, 2, 3]]\n", "foo(a) # make the call" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Taking a look at the output..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from numba import typeof\n", "print(a) # The list looks like a \"normal\" python list\n", "print(type(a)) # but is actually a Numba typed-list container\n", "print(typeof(a)) # and it is type inferred as a `ListType[int64]` (a list of int64 items)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The typed list behaves the same way both inside and outside of jitted functions, the usual operators \"just work\"..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def list_demo(jitted, a):\n", " print(\"jitted: \", jitted)\n", " print(\"input :\",a)\n", " a.pop()\n", " print(\"a.pop() :\", a)\n", " a.extend(a)\n", " print(\"a.extend(a) :\", a)\n", " a.reverse()\n", " print(\"a.reverse() :\", a)\n", " print(\"slice a[::-2] :\", a[::-2])\n", " \n", "list_demo(False, a.copy()) # run the demo on a copy of 'a' in a pure python function\n", "print(\"-\" * 20)\n", "njit(list_demo)(True, a.copy()) # run the demo on a copy of 'a' in a jit compiled function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Further, typed lists can contain considerably more involved structures than those supported in the reflected list implementation. For example, this is a list-of-list-of-typed-dict being returned from a jitted function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def complicated_list_structure():\n", " a = List()\n", " for x in range(4):\n", " tmp = List()\n", " for y in range(3):\n", " d = dict()\n", " d[x] = y\n", " tmp.append(d)\n", " a.append(tmp)\n", " return a\n", "\n", "print(complicated_list_structure())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the same manner as with the ``numba.typed.Dict``, it is also possible to instantiate a ``numba.typed.List`` instance with a specific type. This is useful in the case that type inference cannot automatically infer the type of the list, for example, if type inference would need to cross a function call boundary. The following demonstrates:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def callee(a):\n", " a.append(1j) # the list is a complex128 type\n", "\n", "@njit\n", "def untyped_caller():\n", " x = List() # type of `x` cannot be inferred\n", " callee(x)\n", " return x\n", "\n", "@njit\n", "def typed_caller():\n", " x = List.empty_list(types.complex128) # type of `x` is specified\n", " callee(x)\n", " return x\n", "\n", "# This fails...\n", "try:\n", " untyped_caller()\n", "except errors.TypingError as e:\n", " print(\"Caught error: %s\" % e.msg)\n", "\n", "# This works as expected...\n", "print(\"Works fine: %s\" % typed_caller())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Most fortunately, with thanks to some side effects of the implementation details of the typed-list, the performance is generally good and in a number of use cases excellent, in comparison to the CPython interpreter. \n", "For example, racing a list append of all elements of a large array:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def interpreted_append(arr):\n", " a = []\n", " for x in arr:\n", " a.append(x)\n", " return a\n", "\n", "@njit\n", "def compiled_append(arr):\n", " a = List()\n", " for x in arr:\n", " a.append(x)\n", " return a\n", "\n", "arr = np.random.random(int(1e6)) # array of 1e6 elements\n", "\n", "assert interpreted_append(arr) == list(compiled_append(arr))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Interpreter performance\n", "interpreter = %timeit -o interpreted_append(arr)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# JIT compiled performance\n", "jitted = %timeit -o compiled_append(arr)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Speed up: %sx\" % np.round(interpreter.best/jitted.best, 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This races walking lists and accessing each element..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def walk(x):\n", " count = 0\n", " for v in x:\n", " if v == True:\n", " count += 1\n", " return count\n", "\n", "arr = np.random.random(int(1e6)) < 0.5 # array of 1e6 True/False elements\n", "\n", "typed_list = List()\n", "[typed_list.append(_) for _ in arr]\n", "\n", "builtin_list = [_ for _ in arr]\n", "\n", "# check the results\n", "assert walk(typed_list) == walk.py_func(builtin_list) == walk.py_func(typed_list)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "interpreter = %timeit -o walk.py_func(builtin_list)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "jitted = %timeit -o walk(typed_list)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"Speed up: %sx\" % np.round(interpreter.best/jitted.best, 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Caching of `@jit(parallel=True)` functions \n", "=======\n", "Whilst a small addition on the face of it, the ability to cache functions that are decorated with `@jit(parallel=True)` is a huge improvement for users of Numba's automatic parallelisation. The parallelisation compilation path is the most involved of all those in Numba and being able to cache the compilation results for reuse should drastically improve start up performance in certain applications. A quick demonstration:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit(parallel=True, cache=True)\n", "def parallel():\n", " n = int(1e4)\n", " x = np.zeros((n, n))\n", " y = np.ones((n, n))\n", " a = x + y\n", " b = a * 2\n", " c = a - b\n", " d = c / y + np.sqrt(x)\n", " e = np.sin(d) ** 2 + np.cos(d) ** 2\n", " return e\n", "\n", "parallel()\n", "parallel.stats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Newly supported NumPy functions\n", "=====\n", "This release contains a number of newly supported NumPy functions:\n", "* Selection: `np.select`\n", "* Convenience function: `np.flatnonzero`\n", "* Windowing functions: `np.bartlett`, `np.hamming`, `np.blackman`, `np.hanning`, `np.kaiser`\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def numpy_new():\n", "\n", " arr = np.array([[0, 2], [3 ,0]])\n", " \n", " # np.select\n", " condlist = [arr == 0, arr != 0]\n", " choicelist = [arr ** 2, arr ** 3]\n", " print(\"np.select:\\n\", np.select(condlist, choicelist, 1))\n", " \n", " # np.flatnonzero\n", " print(\"np.flatnonzero:\\n\", np.flatnonzero(arr))\n", "\n", " # windowing functions...\n", " print(\"np.bartlett:\\n\", np.bartlett(5))\n", " print(\"np.blackman:\\n\", np.blackman(5))\n", " print(\"np.hamming:\\n\", np.hamming(5))\n", " print(\"np.hanning:\\n\", np.hanning(5))\n", " print(\"np.kaiser:\\n\", np.kaiser(5, 5))\n", " \n", "numpy_new()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Miscellaneous features\n", "===============\n", "Some new features were added that don't fit anywhere in particular but are still very useful. The `range` function now has accessible `start`, `stop` and `step` attributes and as a follow-on piece of functionality, `operator.contains` now works with `range`. A quick demonstration:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def demo_range():\n", " myrange = range(5, 500, 27)\n", " print(\"start:\", myrange.start)\n", " print(\"stop :\", myrange.stop)\n", " print(\"step :\", myrange.step)\n", " print(32 in myrange)\n", " print(7 in myrange)\n", " \n", "demo_range()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also, the `inspect_types` method on the dispatcher now supports the `signature` kwarg to be symmetric with respect to the other `inspect_*` methods. As an example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@njit\n", "def add_one(x):\n", " return x + 1\n", "\n", "add_one(1)\n", "add_one(1.)\n", "add_one(1j)\n", "\n", "print(\"Known signatures:\", add_one.signatures)\n", "\n", "# show the types with respect to the zeroth signature\n", "add_one.inspect_types(signature=add_one.signatures[0], pretty=True)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }