├── setup.py ├── README.rst ├── LICENSE └── ptime └── __init__.py /setup.py: -------------------------------------------------------------------------------- 1 | from os.path import exists 2 | from setuptools import setup 3 | 4 | setup(name='ptime', 5 | version='0.0.1', 6 | description='IPython magic for parallel profiling', 7 | url='http://github.com/jcrist/ptime/', 8 | maintainer='Jim Crist', 9 | maintainer_email='jiminy.crist@gmail.com', 10 | license='BSD', 11 | packages=['ptime'], 12 | long_description=(open('README.rst').read() if exists('README.rst') 13 | else ''), 14 | zip_safe=False) 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ptime 2 | ===== 3 | 4 | A tool for measuring serial and parallel execution, and comparing the results. 5 | Provides an `IPython magic 6 | `_ ``%ptime``. 7 | This can be useful for measuring the benefits of parallelizing code, including 8 | measuring the effect of the `Global Interpreter Lock 9 | `_ (GIL). 10 | 11 | Example 12 | ------- 13 | 14 | .. code:: 15 | 16 | In [1]: %load_ext ptime 17 | 18 | In [2]: import numpy as np 19 | 20 | In [3]: x = np.ones((5000, 10000)) 21 | 22 | In [4]: %ptime x + x 23 | Total serial time: 0.42 s 24 | Total parallel time: 0.25 s 25 | For a 1.67X speedup across 2 threads 26 | 27 | In [5]: %ptime -n4 x + x # use 4 threads 28 | Total serial time: 0.82 s 29 | Total parallel time: 0.31 s 30 | For a 2.60X speedup across 4 threads 31 | 32 | In [6]: res = %ptime -o x + x # Get the result 33 | Total serial time: 0.41 s 34 | Total parallel time: 0.25 s 35 | For a 1.66X speedup across 2 threads 36 | 37 | In [7]: res.speedup 38 | Out[7]: 1.6610825669011922 39 | 40 | In [8]: %%ptime # Use as a cell magic 41 | ...: x = np.ones((5000, 10000)) 42 | ...: y = x + x 43 | ...: 44 | Total serial time: 0.72 s 45 | Total parallel time: 0.47 s 46 | For a 1.54X speedup across 2 threads 47 | 48 | Install 49 | ------- 50 | 51 | This package is available via pip: 52 | 53 | .. code:: 54 | 55 | pip install ptime 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Jim Crist and Contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /ptime/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | from timeit import default_timer 4 | from threading import Thread 5 | from collections import namedtuple 6 | 7 | from IPython.core.magic import (Magics, line_cell_magic, magics_class, 8 | needs_local_scope) 9 | 10 | __version__ = '0.0.1' 11 | 12 | 13 | def _exec(stmt, glob, local): 14 | exec(stmt, glob, local) 15 | 16 | 17 | def time_exec_serial(stmt, glob, local, n): 18 | start = default_timer() 19 | for i in range(n): 20 | exec(stmt, glob, local.copy()) 21 | stop = default_timer() 22 | return stop - start 23 | 24 | 25 | def time_exec_parallel(stmt, glob, local, n): 26 | threads = [Thread(target=_exec, args=(stmt, glob, local.copy())) 27 | for i in range(n)] 28 | start = default_timer() 29 | for thread in threads: 30 | thread.start() 31 | for thread in threads: 32 | thread.join() 33 | stop = default_timer() 34 | return stop - start 35 | 36 | 37 | class PTimeResult(namedtuple('_PTimeResult', 38 | ('serial_total', 39 | 'parallel_total', 40 | 'nthreads'))): 41 | @property 42 | def percent_speedup(self): 43 | return 100 / self.speedup 44 | 45 | @property 46 | def speedup(self): 47 | return self.serial_total / self.parallel_total 48 | 49 | def __repr__(self): 50 | return ('Total serial time: %.2f s\n' 51 | 'Total parallel time: %.2f s\n' 52 | 'For a %.2fX speedup across %d ' 53 | 'threads') % (self.serial_total, 54 | self.parallel_total, 55 | self.speedup, 56 | self.nthreads) 57 | 58 | 59 | @magics_class 60 | class GILProfilerMagic(Magics): 61 | @needs_local_scope 62 | @line_cell_magic 63 | def ptime(self, line='', cell=None, local_ns=None): 64 | """Measure execution time in serial and parallel and compare. 65 | 66 | Usage, in line mode: 67 | %ptime [-n -o -q] statement 68 | Usage, in cell mode: 69 | %%ptime [-n -o -q] setup_code 70 | code... 71 | code... 72 | 73 | This function can be used both as a line and cell magic: 74 | 75 | - In line mode you can measure a single-line statement (though multiple 76 | ones can be chained with using semicolons). 77 | 78 | - In cell mode, the statement in the first line is used as setup code 79 | (executed but not measured) and the body of the cell is measured. 80 | The cell body has access to any variables created in the setup code. 81 | 82 | Options: 83 | -n: number of threads to use. Default: 2 84 | 85 | -o: Return a PTimeResult object containing ptime run details 86 | 87 | -q: Quiet, do not print result 88 | 89 | Examples 90 | -------- 91 | :: 92 | 93 | In [1]: import numpy as np 94 | 95 | In [2]: x = np.ones((5000, 10000)) 96 | 97 | In [3]: %ptime x + x 98 | Total serial time: 0.42 s 99 | Total parallel time: 0.25 s 100 | For a 1.67X speedup across 2 threads 101 | 102 | In [4]: %%ptime # Use as a cell magic 103 | ...: x = np.ones((5000, 10000)) 104 | ...: y = x + x 105 | ...: 106 | Total serial time: 0.72 s 107 | Total parallel time: 0.47 s 108 | For a 1.54X speedup across 2 threads 109 | """ 110 | opts, stmt = self.parse_options(line, 'n:oq', posix=False, 111 | strict=False) 112 | 113 | if cell is None: 114 | setup = '' 115 | else: 116 | setup = stmt 117 | stmt = cell 118 | 119 | nthreads = int(getattr(opts, 'n', 2)) 120 | return_result = 'o' in opts 121 | quiet = 'q' in opts 122 | 123 | if local_ns: 124 | local = local_ns.copy() 125 | else: 126 | local = {} 127 | 128 | try: 129 | if setup: 130 | exec(setup, self.shell.user_ns, local) 131 | 132 | serial = time_exec_serial(stmt, self.shell.user_ns, local, 133 | nthreads) 134 | parallel = time_exec_parallel(stmt, self.shell.user_ns, local, 135 | nthreads) 136 | except: 137 | self.shell.showtraceback() 138 | return 139 | 140 | result = PTimeResult(serial, parallel, nthreads) 141 | 142 | if not quiet: 143 | print(result) 144 | 145 | if return_result: 146 | return result 147 | 148 | 149 | def load_ipython_extension(ip): 150 | ip.register_magics(GILProfilerMagic) 151 | --------------------------------------------------------------------------------