|
6 | 6 |
|
7 | 7 | .. _topics-cute-profile: |
8 | 8 |
|
9 | | -:mod:`cute_profile` - documentation not written |
10 | | -====================================== |
| 9 | +:mod:`cute_profile` |
| 10 | +=================== |
| 11 | + |
| 12 | +The :mod:`cute_profile` module allows you to profile your code (i.e. find out |
| 13 | +which parts make it slow) by giving a nicer interface to the :mod:`cProfile` |
| 14 | +library from Python's standard library. |
| 15 | + |
| 16 | + |
| 17 | +What is "profiling"? |
| 18 | +-------------------- |
| 19 | + |
| 20 | +(Programmers experienced with profilers may skip this section.) |
| 21 | + |
| 22 | +To "profile" a piece of code means to run it while checking how long it takes, |
| 23 | +and how long each function call inside the code takes. When you use a |
| 24 | +"profiler" to profile your program, you get a table of (a) all the functions |
| 25 | +calls that were made by the program, (b) how many times each function was |
| 26 | +called and (c) how long the function calls took. |
| 27 | + |
| 28 | +A profiler is an indispensable programming tool, because it allows the |
| 29 | +programmer to understand which parts of his code take the longest. Usually, |
| 30 | +when using a profiler, you discover that only a few small parts of his code |
| 31 | +take most of the runtime of your program. And quite often, it's not the parts |
| 32 | +of code that you *thought* were the slow ones. |
| 33 | + |
| 34 | +Once you realize which parts of the program cause slowness, you can focus your |
| 35 | +efforts on those problematic parts only, optimizing them or possibly |
| 36 | +redesigning the way they work so they're not slow anymore. Then the whole |
| 37 | +program becomes faster. |
| 38 | + |
| 39 | +Profiling Python code with :mod:`cute_profile` |
| 40 | +---------------------------------------------- |
| 41 | + |
| 42 | +Python supplies a module called :mod:`cProfile` in its standard library. |
| 43 | +:mod:`cProfile` is a good profiler, but its interface can be inconvenient to |
| 44 | +work with. The :mod:`cute_profile` module has a more flexible interface, and it |
| 45 | +uses :mod:`cProfile` under the hood to do the actual profiling. |
| 46 | + |
| 47 | +Let's profile an example program. Our example would be a function called |
| 48 | +``get_perfects``, which finds `perfect numbers`_: |
| 49 | + |
| 50 | + >>> def get_divisors(x): |
| 51 | + ... '''Get all the integer divisors of `x`.''' |
| 52 | + ... return [i for i in xrange(1, x) if (x % i == 0)] |
| 53 | + ... |
| 54 | + >>> def is_perfect(x): |
| 55 | + ... '''Is the number `x` perfect?''' |
| 56 | + ... return sum(get_divisors(x)) == x |
| 57 | + ... |
| 58 | + >>> def get_perfects(top): |
| 59 | + ... '''Get all the perfect numbers up to the number `top`.''' |
| 60 | + ... return [i for i in xrange(1, top) if is_perfect(i)] |
| 61 | + >>> print(get_perfects(20000)) |
| 62 | + |
| 63 | +The result is ``[6, 28, 496, 8128]``. However, this function takes a few |
| 64 | +seconds to run. That's fairly long. Let's use :mod:`cute_profile` to find out |
| 65 | +*why* this function is taking so long. We'll add the |
| 66 | +:func:`cute_profile.profile_ready` decorator around `get_perfects`: |
| 67 | + |
| 68 | + >>> from python_toolbox import cute_profile |
| 69 | + >>> @cute_profile.profile_ready() |
| 70 | + ... def get_perfects(top): |
| 71 | + ... '''Get all the perfect numbers up to the number `top`.''' |
| 72 | + ... return [i for i in xrange(1, top) if is_perfect(i)] |
| 73 | + |
| 74 | + |
| 75 | +Now before we run ``get_perfects``, we set it to profile: |
| 76 | + |
| 77 | + >>> get_perfects.profiling_on = True |
| 78 | + |
| 79 | +And now we run it: |
| 80 | + |
| 81 | + >>> print(get_perfects(20000)) |
| 82 | + |
| 83 | +We still get the same result, but now a profiling table gets printed: |
| 84 | + |
| 85 | +6000 function calls in 6.142 seconds |
| 86 | + |
| 87 | + Ordered by: cumulative time |
| 88 | + |
| 89 | + ncalls tottime percall cumtime percall filename:lineno(function) |
| 90 | + 1 0.000 0.000 6.142 6.142 :1() |
| 91 | + 1 0.025 0.025 6.142 6.142 perfect_numbers.py:9(get_perfects) |
| 92 | + 1999 0.045 0.000 6.117 0.003 perfect_numbers.py:6(is_perfect) |
| 93 | + 1999 6.060 0.003 6.060 0.003 perfect_numbers.py:3(get_divisors) |
| 94 | + 1999 0.012 0.000 0.012 0.000 {sum} |
| 95 | + 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} |
| 96 | +</pre> |
| 97 | +This table shows how long each function took. If you want to understand *exactly* what each number says in this table, <a href="http://docs.python.org/library/profile.html#cProfile.run">there's an explanation here</a>. |
| 98 | +The <code>tottime</code> column says how much time was spent inside this function, across all calls, and without counting the time that was spent in sub-functions. See how the <code>get_divisors</code> function in our example has a very high <code>tottime</code> of 6.060. This means that <code>get_divisors</code> is what's causing our program to run slow, and if we'll want to optimize the program, we should try to come up with a smarter way of finding all of a number's divisors than going one-by-one over all numbers. |
| 99 | +<code>profile_ready</code> has a bunch of other options that I won't explore there. In brief: |
| 100 | +<ul><li>The <code>condition</code> argument is something like a "breakpoint condition" in an IDE: It can be a function, usually a lambda, that takes the decorated function and any arguments and returns whether or not to profile it this time.</li> |
| 101 | +<li><code>off_after</code> means whether you want the function to stop being profiled after being profiled one time. Default is <code>True</code>.</li> |
| 102 | +<li><code>sort</code> is an integer saying by which column the final results table should be sorted.</li> |
| 103 | + |
| 104 | + |
| 105 | +.. _perfect numbers: http://en.wikipedia.org/wiki/Perfect_number |
0 commit comments