{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Profiling and Optimization Hands-On" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from math import sin, cos\n", "import matplotlib.pyplot as plt\n", "from time import sleep\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%sh\n", "\n", "mamba install memory_profiler line_profiler" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A common problem: Which implementation is faster?\n", "\n", "Example: Given a large 2D array, we will explore different ways to create the array and to calculate its mean. Determine which one is fastest, using the `%timeit` notebook function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### setup: define our 2D array:\n", "\n", "We'll make some dummy test data that looks like:\n", "\n", "$M_{ij} = \\sin(i)\\cos(0.1 j)$\n", "\n", "and we will construct this array in multiple ways." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_array_loop(N,M):\n", " arr = []\n", " for y in range(M):\n", " row = []\n", " for x in range(N):\n", " row.append(sin(x)*cos(0.1*y))\n", " arr.append(row)\n", " return arr\n", "\n", "def create_array_list(N,M):\n", " \"\"\"a 2D array using a list-comprehension\"\"\"\n", " return [[sin(x)*cos(0.1*y) for x in range(N)] for y in range(M)] \n", "\n", "def create_array_np(N,M):\n", " \"\"\" a 2D array using numpy\"\"\"\n", " X,Y = np.meshgrid(np.arange(N), np.arange(M))\n", " return np.sin(X)*np.cos(0.1*Y)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's first just plot the arrays, to see if they are the same:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N=30; M=10 # our array dimensions\n", "plt.figure(figsize=(12,5))\n", "plt.subplot(1,3,1)\n", "plt.imshow( create_array_loop(N,M))\n", "plt.subplot(1,3,2)\n", "plt.imshow( create_array_list(N,M))\n", "plt.subplot(1,3,3)\n", "plt.imshow( create_array_np(N,M))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Task: determine which array creation routine is fastest\n", "\n", "And make a plot of the speed of each! Does the result change much when the array size becomes larger? Try much larger sizes for N and M\n", "\n", "Hint: use the `%timeit -o` magic function to have `%timeit` return results (see the timeit help)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SOLUTION:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: to minimize the uncertainty, try increasing the problem size! " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N=30; M=10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Useful tip**: timeit has a *-o* option that lets you save the results for later comparison!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = {} # a place to record out time statistics" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t['loop'] = %timeit -o create_array_loop(N,M)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t['list'] = %timeit -o create_array_list(N,M)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t['numpy'] = %timeit -o create_array_np(N,M)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def plot_performance(time_dict):\n", " mean = [t.average for t in time_dict.values()]\n", " std = [t.stdev for t in time_dict.values()]\n", " x = range(len(time_dict))\n", " plt.errorbar(x, mean, yerr=std, lw=3, fmt=\"o\") \n", " plt.xticks( np.arange(len(time_dict)), time_dict.keys())\n", " plt.ylabel(\"average execution time (s)\")\n", " plt.grid()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot_performance(t)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Task 2: determine the fastest way to find the mean of our array\n", "\n", "note that `create_array_list()` and `create_array_loop` both return a list-of-lists, while `create_array_np` returns a 2D numpy array. There are multiple ways to compute the mean of these arrays. See again which is fastest!\n", "\n", "try at least:\n", "\n", "1. using the built-in python `sum` function and either a for-loop or list-comprehension\n", "2. using pure numpy (e.g. `array.mean()`)\n", "3. other ways you can think of!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### SOLUTION" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N=100; M=100\n", "a_list = create_array_list(N,M)\n", "a_np = create_array_np(N,M)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sum([sum(x) for x in a_list])/(N*M)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a_np.mean()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2 = {}\n", "t2[\"numpy a.mean\"] = %timeit -o a_np.mean()\n", "t2[\"numpy mean(a)\"] = %timeit -o np.mean(a_np)\n", "t2[\"sum(a)\"] = %timeit -o sum([sum(x) for x in a_list])/(N*M)\n", "plot_performance(t2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Using a profiler\n", "\n", "A profiler gives you speed measurements of *all functions in your code at once* (and all their dependencies)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def slow_function(x):\n", " sleep(1)\n", " return x**2\n", "\n", "def faster_function(x):\n", " return x**2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%prun -r\n", "\n", "for ii in range(5):\n", " x = slow_function(ii)\n", " y = faster_function(ii)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stats = _" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stats.sort_stats(\"call\").print_stats()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What about line profiling?\n", "\n", "The syntax is: \n", "```\n", "%lprun -f \n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load_ext line_profiler" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def run_all():\n", " for ii in range(5):\n", " x = slow_function(ii)\n", " y = faster_function(ii)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%lprun -f run_all run_all()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try adding also `-f slow_function` to see inside that function as well!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%lprun -f create_array_loop create_array_loop(100,100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Memory Profiling" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from math import sin, cos" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load_ext memory_profiler" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%memit np.sum(np.sin(np.arange(1000000)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%memit sum(sin(x) for x in range(1000000))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One problem: in a notebook, this is measuring total memory, not just for the function being run, and is affected by garbage collection. Try instead making modules:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile tmp_sum.py\n", "\n", "import numpy as np\n", "import math\n", "\n", "def do_some_sums(n=100_000): \n", " sum1 = np.sum(np.sin(np.arange(n)))\n", " sum2 = sum(math.sin(x) for x in range(n))\n", " return sum1+sum2\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from tmp_sum import do_some_sums" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%mprun -f do_some_sums do_some_sums(500_000)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "school2021", "language": "python", "name": "school2021" }, "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.9.4" } }, "nbformat": 4, "nbformat_minor": 4 }