Categories
optimization performance profiling python time-complexity

How do I profile a Python script?

1574

Project Euler and other coding contests often have a maximum time to run or people boast of how fast their particular solution runs. With Python, sometimes the approaches are somewhat kludgey – i.e., adding timing code to __main__.

What is a good way to profile how long a Python program takes to run?

3

  • 137

    Project euler programs shouldn’t need profiling. Either you have an algorithm that works in under a minute, or you have entirely the wrong algorithm. “Tuning” is rarely appropriate. You generally have to take a fresh approach.

    – S.Lott

    Feb 24, 2009 at 16:52

  • 157

    S.Lott: Profiling is often a helpful way to determine which subroutines are slow. Subroutines that take a long time are great candidates for algorithmic improvement.

    Sep 14, 2012 at 3:25

  • 4

    It’s worth mentioning two packages: py-spy and nvtx for cases when the code runs on CPUs and/or GPUs.

    – 0x90

    Mar 12, 2021 at 4:36


1630

Python includes a profiler called cProfile. It not only gives the total running time, but also times each function separately, and tells you how many times each function was called, making it easy to determine where you should make optimizations.

You can call it from within your code, or from the interpreter, like this:

import cProfile
cProfile.run('foo()')

Even more usefully, you can invoke the cProfile when running a script:

python -m cProfile myscript.py

To make it even easier, I made a little batch file called ‘profile.bat’:

python -m cProfile %1

So all I have to do is run:

profile euler048.py

And I get this:

1007 function calls in 0.061 CPU seconds

Ordered by: standard name
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.061    0.061 <string>:1(<module>)
 1000    0.051    0.000    0.051    0.000 euler048.py:2(<lambda>)
    1    0.005    0.005    0.061    0.061 euler048.py:2(<module>)
    1    0.000    0.000    0.061    0.061 {execfile}
    1    0.002    0.002    0.053    0.053 {map}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler objects}
    1    0.000    0.000    0.000    0.000 {range}
    1    0.003    0.003    0.003    0.003 {sum}

EDIT: Updated link to a good video resource from PyCon 2013 titled
Python Profiling
Also via YouTube.

17

  • 289

    Also it is useful to sort the results, that can be done by -s switch, example: ‘-s time’. You can use cumulative/name/time/file sorting options.

    – Jiri

    Feb 25, 2009 at 17:41

  • 27

    It is also worth noting that you can use the cProfile module from ipython using the magic function %prun (profile run). First import your module, and then call the main function with %prun: import euler048; %prun euler048.main()

    Mar 31, 2014 at 19:58

  • 69

    For visualizing cProfile dumps (created by python -m cProfile -o <out.profile> <script>), RunSnakeRun, invoked as runsnake <out.profile> is invaluable.

    May 5, 2014 at 1:33


  • 17

    @NeilG even for python 3, cprofile is still recommended over profile.

    Jan 4, 2015 at 2:43


  • 45

    For visualizing cProfile dumps, RunSnakeRun hasn’t been updated since 2011 and doesn’t support python3. You should use snakeviz instead

    Dec 11, 2017 at 9:48

503

A while ago I made pycallgraph which generates a visualisation from your Python code. Edit: I’ve updated the example to work with 3.3, the latest release as of this writing.

After a pip install pycallgraph and installing GraphViz you can run it from the command line:

pycallgraph graphviz -- ./mypythonscript.py

Or, you can profile particular parts of your code:

from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

with PyCallGraph(output=GraphvizOutput()):
    code_to_profile()

Either of these will generate a pycallgraph.png file similar to the image below:

enter image description here

13

  • 60

    Are you coloring based on the amount of calls? If so, you should color based on time because the function with the most calls isn’t always the one that takes the most time.

    – red

    Aug 6, 2013 at 12:21

  • 28

    @red You can customise colours however you like, and even independently for each measurement. For example red for calls, blue for time, green for memory usage.

    – gak

    Aug 6, 2013 at 22:18

  • 2

    getting this errorTraceback (most recent call last): /pycallgraph.py", line 90, in generate output.done() File "/net_downloaded/pycallgraph-develop/pycallgraph/output/graphviz.py", line 94, in done source = self.generate() File "/net_downloaded/pycallgraph-develop/pycallgraph/output/graphviz.py", line 143, in generate indent_join.join(self.generate_attributes()), File "/net_downloaded/pycallgraph-develop/pycallgraph/output/graphviz.py", line 169, in generate_attributes section, self.attrs_from_dict(attrs), ValueError: zero length field name in format

    Aug 18, 2014 at 11:39

  • 4

    I updated this to mention that you need to install GraphViz for things to work as described. On Ubuntu this is just sudo apt-get install graphviz.

    – mlissner

    Nov 18, 2015 at 17:55

  • 9

    The github page states that this project is abandoned … 🙁

    – A. Rabus

    Nov 12, 2019 at 12:45


228

It’s worth pointing out that using the profiler only works (by default) on the main thread, and you won’t get any information from other threads if you use them. This can be a bit of a gotcha as it is completely unmentioned in the profiler documentation.

If you also want to profile threads, you’ll want to look at the threading.setprofile() function in the docs.

You could also create your own threading.Thread subclass to do it:

class ProfiledThread(threading.Thread):
    # Overrides threading.Thread.run()
    def run(self):
        profiler = cProfile.Profile()
        try:
            return profiler.runcall(threading.Thread.run, self)
        finally:
            profiler.dump_stats('myprofile-%d.profile' % (self.ident,))

and use that ProfiledThread class instead of the standard one. It might give you more flexibility, but I’m not sure it’s worth it, especially if you are using third-party code which wouldn’t use your class.

7

  • 1

    I don’t see any reference to runcall in the documentation either. Giving a look at cProfile.py, I’m not sure why you use the threading.Thread.run function nor self as argument. I’d have expected to see a reference to another thread’s run method here.

    – PypeBros

    Nov 9, 2011 at 11:14

  • It’s not in the documentation, but it is in the module. See hg.python.org/cpython/file/6bf07db23445/Lib/cProfile.py#l140. That allows you to profile a specific function call, and in our case we want to profile the Thread’s target function, which is what the threading.Thread.run() call executes. But as I said in the answer, it’s probably not worth it to subclass Thread, since any third-party code won’t use it, and to instead use threading.setprofile().

    – Joe Shaw

    Nov 9, 2011 at 14:04


  • 11

    wrapping the code with profiler.enable() and profiler.disable() seems to work quite well, too. That’s basically what runcall do and it doesn’t enforce any number of argument or similar things.

    – PypeBros

    Nov 10, 2011 at 10:58

  • 1

    I combined my own stackoverflow.com/questions/10748118/… with ddaa.net/blog/python/lsprof-calltree and it kindof works ;!-)

    Jul 11, 2012 at 15:05

  • 1

    Joe, do you know how the profiler plays with asyncio in Python 3.4?

    Jun 17, 2015 at 22:44