|
37 | 37 | "Early in developing your algorithm, it can be counterproductive to worry about such things. As Donald Knuth famously quipped, \"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.\"\n", |
38 | 38 | "\n", |
39 | 39 | "But once you have your code working, it can be useful to dig into its efficiency a bit.\n", |
40 | | - "Sometimes it's useful to check the execution time of a given command or set of commands; other times it's useful to dig into a multiline process and determine where the bottleneck lies in some complicated series of operations.\n", |
| 40 | + "Sometimes it's useful to check the execution time of a given command or set of commands; other times it's useful to examine a multiline process and determine where the bottleneck lies in some complicated series of operations.\n", |
41 | 41 | "IPython provides access to a wide array of functionality for this kind of timing and profiling of code.\n", |
42 | 42 | "Here we'll discuss the following IPython magic commands:\n", |
43 | 43 | "\n", |
|
69 | 69 | "name": "stdout", |
70 | 70 | "output_type": "stream", |
71 | 71 | "text": [ |
72 | | - "100000 loops, best of 3: 1.54 µs per loop\n" |
| 72 | + "1.53 µs ± 47.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)\n" |
73 | 73 | ] |
74 | 74 | } |
75 | 75 | ], |
|
94 | 94 | "name": "stdout", |
95 | 95 | "output_type": "stream", |
96 | 96 | "text": [ |
97 | | - "1 loops, best of 3: 407 ms per loop\n" |
| 97 | + "536 ms ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" |
98 | 98 | ] |
99 | 99 | } |
100 | 100 | ], |
|
124 | 124 | "name": "stdout", |
125 | 125 | "output_type": "stream", |
126 | 126 | "text": [ |
127 | | - "100 loops, best of 3: 1.9 ms per loop\n" |
| 127 | + "1.71 ms ± 334 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" |
128 | 128 | ] |
129 | 129 | } |
130 | 130 | ], |
|
152 | 152 | "output_type": "stream", |
153 | 153 | "text": [ |
154 | 154 | "sorting an unsorted list:\n", |
155 | | - "CPU times: user 40.6 ms, sys: 896 µs, total: 41.5 ms\n", |
156 | | - "Wall time: 41.5 ms\n" |
| 155 | + "CPU times: user 31.3 ms, sys: 686 µs, total: 32 ms\n", |
| 156 | + "Wall time: 33.3 ms\n" |
157 | 157 | ] |
158 | 158 | } |
159 | 159 | ], |
|
174 | 174 | "output_type": "stream", |
175 | 175 | "text": [ |
176 | 176 | "sorting an already sorted list:\n", |
177 | | - "CPU times: user 8.18 ms, sys: 10 µs, total: 8.19 ms\n", |
178 | | - "Wall time: 8.24 ms\n" |
| 177 | + "CPU times: user 5.19 ms, sys: 268 µs, total: 5.46 ms\n", |
| 178 | + "Wall time: 14.1 ms\n" |
179 | 179 | ] |
180 | 180 | } |
181 | 181 | ], |
|
205 | 205 | "name": "stdout", |
206 | 206 | "output_type": "stream", |
207 | 207 | "text": [ |
208 | | - "CPU times: user 504 ms, sys: 979 µs, total: 505 ms\n", |
209 | | - "Wall time: 505 ms\n" |
| 208 | + "CPU times: user 655 ms, sys: 5.68 ms, total: 661 ms\n", |
| 209 | + "Wall time: 710 ms\n" |
210 | 210 | ] |
211 | 211 | } |
212 | 212 | ], |
|
269 | 269 | "text": [ |
270 | 270 | " " |
271 | 271 | ] |
| 272 | + }, |
| 273 | + { |
| 274 | + "data": { |
| 275 | + "text/plain": [ |
| 276 | + " 14 function calls in 0.932 seconds\n", |
| 277 | + "\n", |
| 278 | + " Ordered by: internal time\n", |
| 279 | + "\n", |
| 280 | + " ncalls tottime percall cumtime percall filename:lineno(function)\n", |
| 281 | + " 5 0.808 0.162 0.808 0.162 <ipython-input-7-f105717832a2>:4(<listcomp>)\n", |
| 282 | + " 5 0.066 0.013 0.066 0.013 {built-in method builtins.sum}\n", |
| 283 | + " 1 0.044 0.044 0.918 0.918 <ipython-input-7-f105717832a2>:1(sum_of_lists)\n", |
| 284 | + " 1 0.014 0.014 0.932 0.932 <string>:1(<module>)\n", |
| 285 | + " 1 0.000 0.000 0.932 0.932 {built-in method builtins.exec}\n", |
| 286 | + " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}" |
| 287 | + ] |
| 288 | + }, |
| 289 | + "metadata": {}, |
| 290 | + "output_type": "display_data" |
272 | 291 | } |
273 | 292 | ], |
274 | 293 | "source": [ |
|
279 | 298 | "cell_type": "markdown", |
280 | 299 | "metadata": {}, |
281 | 300 | "source": [ |
282 | | - "In the notebook, the output is printed to the pager, and looks something like this:\n", |
283 | | - "\n", |
284 | | - "```\n", |
285 | | - "14 function calls in 0.714 seconds\n", |
286 | | - "\n", |
287 | | - " Ordered by: internal time\n", |
288 | | - "\n", |
289 | | - " ncalls tottime percall cumtime percall filename:lineno(function)\n", |
290 | | - " 5 0.599 0.120 0.599 0.120 <ipython-input-19>:4(<listcomp>)\n", |
291 | | - " 5 0.064 0.013 0.064 0.013 {built-in method sum}\n", |
292 | | - " 1 0.036 0.036 0.699 0.699 <ipython-input-19>:1(sum_of_lists)\n", |
293 | | - " 1 0.014 0.014 0.714 0.714 <string>:1(<module>)\n", |
294 | | - " 1 0.000 0.000 0.714 0.714 {built-in method exec}\n", |
295 | | - "```\n", |
296 | | - "\n", |
297 | 301 | "The result is a table that indicates, in order of total time on each function call, where the execution is spending the most time. In this case, the bulk of execution time is in the list comprehension inside ``sum_of_lists``.\n", |
298 | 302 | "From here, we could start thinking about what changes we might make to improve the performance in the algorithm.\n", |
299 | 303 | "\n", |
|
337 | 341 | "cell_type": "code", |
338 | 342 | "execution_count": 10, |
339 | 343 | "metadata": {}, |
340 | | - "outputs": [], |
| 344 | + "outputs": [ |
| 345 | + { |
| 346 | + "data": { |
| 347 | + "text/plain": [ |
| 348 | + "Timer unit: 1e-06 s\n", |
| 349 | + "\n", |
| 350 | + "Total time: 0.014803 s\n", |
| 351 | + "File: <ipython-input-7-f105717832a2>\n", |
| 352 | + "Function: sum_of_lists at line 1\n", |
| 353 | + "\n", |
| 354 | + "Line # Hits Time Per Hit % Time Line Contents\n", |
| 355 | + "==============================================================\n", |
| 356 | + " 1 def sum_of_lists(N):\n", |
| 357 | + " 2 1 6.0 6.0 0.0 total = 0\n", |
| 358 | + " 3 6 13.0 2.2 0.1 for i in range(5):\n", |
| 359 | + " 4 5 14242.0 2848.4 96.2 L = [j ^ (j >> i) for j in range(N)]\n", |
| 360 | + " 5 5 541.0 108.2 3.7 total += sum(L)\n", |
| 361 | + " 6 1 1.0 1.0 0.0 return total" |
| 362 | + ] |
| 363 | + }, |
| 364 | + "metadata": {}, |
| 365 | + "output_type": "display_data" |
| 366 | + } |
| 367 | + ], |
341 | 368 | "source": [ |
342 | 369 | "%lprun -f sum_of_lists sum_of_lists(5000)" |
343 | 370 | ] |
|
346 | 373 | "cell_type": "markdown", |
347 | 374 | "metadata": {}, |
348 | 375 | "source": [ |
349 | | - "As before, the notebook sends the result to the pager, but it looks something like this:\n", |
350 | | - "\n", |
351 | | - "```\n", |
352 | | - "Timer unit: 1e-06 s\n", |
353 | | - "\n", |
354 | | - "Total time: 0.009382 s\n", |
355 | | - "File: <ipython-input-19-fa2be176cc3e>\n", |
356 | | - "Function: sum_of_lists at line 1\n", |
357 | | - "\n", |
358 | | - "Line # Hits Time Per Hit % Time Line Contents\n", |
359 | | - "==============================================================\n", |
360 | | - " 1 def sum_of_lists(N):\n", |
361 | | - " 2 1 2 2.0 0.0 total = 0\n", |
362 | | - " 3 6 8 1.3 0.1 for i in range(5):\n", |
363 | | - " 4 5 9001 1800.2 95.9 L = [j ^ (j >> i) for j in range(N)]\n", |
364 | | - " 5 5 371 74.2 4.0 total += sum(L)\n", |
365 | | - " 6 1 0 0.0 0.0 return total\n", |
366 | | - "```\n", |
367 | | - "\n", |
368 | 376 | "The information at the top gives us the key to reading the results: the time is reported in microseconds and we can see where the program is spending the most time.\n", |
369 | 377 | "At this point, we may be able to use this information to modify aspects of the script and make it perform better for our desired use case.\n", |
370 | 378 | "\n", |
|
390 | 398 | }, |
391 | 399 | { |
392 | 400 | "cell_type": "code", |
393 | | - "execution_count": 12, |
| 401 | + "execution_count": 11, |
394 | 402 | "metadata": {}, |
395 | 403 | "outputs": [], |
396 | 404 | "source": [ |
|
407 | 415 | }, |
408 | 416 | { |
409 | 417 | "cell_type": "code", |
410 | | - "execution_count": 13, |
| 418 | + "execution_count": 12, |
411 | 419 | "metadata": {}, |
412 | 420 | "outputs": [ |
413 | 421 | { |
414 | 422 | "name": "stdout", |
415 | 423 | "output_type": "stream", |
416 | 424 | "text": [ |
417 | | - "peak memory: 100.08 MiB, increment: 61.36 MiB\n" |
| 425 | + "peak memory: 141.70 MiB, increment: 75.65 MiB\n" |
418 | 426 | ] |
419 | 427 | } |
420 | 428 | ], |
|
426 | 434 | "cell_type": "markdown", |
427 | 435 | "metadata": {}, |
428 | 436 | "source": [ |
429 | | - "We see that this function uses about 100 MB of memory.\n", |
| 437 | + "We see that this function uses about 140 MB of memory.\n", |
430 | 438 | "\n", |
431 | 439 | "For a line-by-line description of memory use, we can use the ``%mprun`` magic.\n", |
432 | 440 | "Unfortunately, this magic works only for functions defined in separate modules rather than the notebook itself, so we'll start by using the ``%%file`` magic to create a simple module called ``mprun_demo.py``, which contains our ``sum_of_lists`` function, with one addition that will make our memory profiling results more clear:" |
433 | 441 | ] |
434 | 442 | }, |
435 | 443 | { |
436 | 444 | "cell_type": "code", |
437 | | - "execution_count": 14, |
| 445 | + "execution_count": 13, |
438 | 446 | "metadata": {}, |
439 | 447 | "outputs": [ |
440 | 448 | { |
|
465 | 473 | }, |
466 | 474 | { |
467 | 475 | "cell_type": "code", |
468 | | - "execution_count": 15, |
| 476 | + "execution_count": 14, |
469 | 477 | "metadata": {}, |
470 | 478 | "outputs": [ |
471 | 479 | { |
|
474 | 482 | "text": [ |
475 | 483 | "\n" |
476 | 484 | ] |
| 485 | + }, |
| 486 | + { |
| 487 | + "data": { |
| 488 | + "text/plain": [ |
| 489 | + "Filename: /Users/jakevdp/github/jakevdp/PythonDataScienceHandbook/notebooks_v2/mprun_demo.py\n", |
| 490 | + "\n", |
| 491 | + "Line # Mem usage Increment Occurences Line Contents\n", |
| 492 | + "============================================================\n", |
| 493 | + " 1 66.7 MiB 66.7 MiB 1 def sum_of_lists(N):\n", |
| 494 | + " 2 66.7 MiB 0.0 MiB 1 total = 0\n", |
| 495 | + " 3 75.1 MiB 8.4 MiB 6 for i in range(5):\n", |
| 496 | + " 4 105.9 MiB 30.8 MiB 5000015 L = [j ^ (j >> i) for j in range(N)]\n", |
| 497 | + " 5 109.8 MiB 3.8 MiB 5 total += sum(L)\n", |
| 498 | + " 6 75.1 MiB -34.6 MiB 5 del L # remove reference to L\n", |
| 499 | + " 7 66.9 MiB -8.2 MiB 1 return total" |
| 500 | + ] |
| 501 | + }, |
| 502 | + "metadata": {}, |
| 503 | + "output_type": "display_data" |
477 | 504 | } |
478 | 505 | ], |
479 | 506 | "source": [ |
|
485 | 512 | "cell_type": "markdown", |
486 | 513 | "metadata": {}, |
487 | 514 | "source": [ |
488 | | - "The result, printed to the pager, gives us a summary of the memory use of the function, and looks something like this:\n", |
489 | | - "```\n", |
490 | | - "Filename: ./mprun_demo.py\n", |
491 | | - "\n", |
492 | | - "Line # Mem usage Increment Line Contents\n", |
493 | | - "================================================\n", |
494 | | - " 4 71.9 MiB 0.0 MiB L = [j ^ (j >> i) for j in range(N)]\n", |
495 | | - "\n", |
496 | | - "\n", |
497 | | - "Filename: ./mprun_demo.py\n", |
498 | | - "\n", |
499 | | - "Line # Mem usage Increment Line Contents\n", |
500 | | - "================================================\n", |
501 | | - " 1 39.0 MiB 0.0 MiB def sum_of_lists(N):\n", |
502 | | - " 2 39.0 MiB 0.0 MiB total = 0\n", |
503 | | - " 3 46.5 MiB 7.5 MiB for i in range(5):\n", |
504 | | - " 4 71.9 MiB 25.4 MiB L = [j ^ (j >> i) for j in range(N)]\n", |
505 | | - " 5 71.9 MiB 0.0 MiB total += sum(L)\n", |
506 | | - " 6 46.5 MiB -25.4 MiB del L # remove reference to L\n", |
507 | | - " 7 39.1 MiB -7.4 MiB return total\n", |
508 | | - "```\n", |
509 | | - "Here the ``Increment`` column tells us how much each line affects the total memory budget: observe that when we create and delete the list ``L``, we are adding about 25 MB of memory usage.\n", |
| 515 | + "Here the ``Increment`` column tells us how much each line affects the total memory budget: observe that when we create and delete the list ``L``, we are adding about 30 MB of memory usage.\n", |
510 | 516 | "This is on top of the background memory usage from the Python interpreter itself.\n", |
511 | 517 | "\n", |
512 | 518 | "For more information on ``%memit`` and ``%mprun``, as well as their available options, use the IPython help functionality (i.e., type ``%memit?`` at the IPython prompt)." |
|
529 | 535 | "formats": "ipynb,md" |
530 | 536 | }, |
531 | 537 | "kernelspec": { |
532 | | - "display_name": "Python [default]", |
| 538 | + "display_name": "Python 3", |
533 | 539 | "language": "python", |
534 | 540 | "name": "python3" |
535 | 541 | }, |
|
543 | 549 | "name": "python", |
544 | 550 | "nbconvert_exporter": "python", |
545 | 551 | "pygments_lexer": "ipython3", |
546 | | - "version": "3.6.1" |
| 552 | + "version": "3.9.2" |
547 | 553 | } |
548 | 554 | }, |
549 | 555 | "nbformat": 4, |
550 | | - "nbformat_minor": 1 |
| 556 | + "nbformat_minor": 4 |
551 | 557 | } |
0 commit comments