{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Basic profiling\n", "\n", "This demo assumes you are familiar with the basics of running an Intrepydd, which is covered in the [\"Hello, world!\" demo](./001-hello-world.ipynb), as well as the principle of [type specialization](./002-typing-basics.ipynb)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def sum_elements(xs):\n", " '''Sums all elements in a 3-D array `xs`.'''\n", " assert len(xs.shape) == 3\n", " s = 0.0\n", " for i in range(xs.shape[0]):\n", " for j in range(xs.shape[1]):\n", " for k in range(xs.shape[2]):\n", " s += xs[i, j, k]\n", " return s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here is a function to test this function. We'll reuse this function later to test an Intrepydd version." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_code(fun=sum_elements):\n", " from numpy import arange\n", " xs = arange(10000000).reshape(1000,100,100).astype('double')\n", " return fun(xs)\n", " \n", "test_code()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basic profiling ###\n", "\n", "To help find bottlenecks, you can use any of Python's standard tools for timing or profiling. Here, we show an example of using the [`line_profiler`](https://jakevdp.github.io/PythonDataScienceHandbook/01.07-timing-and-profiling.html), which gives you a line-by-line breakdown of where time is spent. Its use in Jupyter requires a magic command to load the module on first use:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load_ext line_profiler" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once loaded, you can then use the `%lprun` magic to invoke the profiler on any code statement. For example, let's apply it to the tester function (`test_code()`) again. The additional `-f <fun>` argument tells the profiler to only consider the statements, line-by-line, in the body of the specified function `<fun>`. (You can supply the `-f` argument multiple times to record these data for different function bodies.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%lprun -f test_code test_code()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Reimplement using Intrepydd ###\n", "\n", "Let's apply the technique of [type specialization](./jupyter-demo2.ipynb) to speed up this code. It requires minimal changes in this case: adding types to the signature and return value, and replacing Numpy field variable references with their corresponding functions (i.e., `xs.shape[0]` with `shape(xs, 0)`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile demo3.pydd\n", "# demo3.pydd\n", "\n", "def sum_elements(xs: Array(double, 3)) -> double:\n", " '''Sums all elements in a 3-D array `xs`. (Intrepydd version)'''\n", " s = 0.0\n", " for i in range(shape(xs, 0)):\n", " for j in range(shape(xs, 1)):\n", " for k in range(shape(xs, 2)):\n", " s += xs[i, j, k]\n", " return s\n", "\n", "# eof" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, compile with Intrepydd:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!pyddc demo3.pydd" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And finally, let's load this new module and re-run the tester:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import demo3\n", "test_code(demo3.sum_elements)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If everything went well, you should see the same numerical output as with the original version. Now let's see if we're any faster:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%lprun -f test_code test_code(demo3.sum_elements)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Other timers.** Of course, since you are operating in Jupyter, you can use any timing or profiling tool at your disposal. For instance, here is how we can use the built-in `%timeit` magic function to programmatically measure the time and report the speedup of the two versions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "baseline_time = %timeit -o test_code()\n", "intrepydd_time = %timeit -o test_code(demo3.sum_elements)\n", "print(\"Speedup: ~ {:.1f}x\".format(baseline_time.best / intrepydd_time.average))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summary and next steps ###\n", "\n", "A key first step in enabling higher performance is type specialization. The first way you do that in Intrepydd is by modifying the signatures of your function definitions to include annotations." ] } ], "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 }