company_banner

Профилирование и отладка Python, инструменты

    В предыдущей статье мы на практике разобрались, где и в каких случаях можно использовать ручное профилирование, а так же познакомились со статистическими профайлерами.

    Сегодня мы познакомимся с основной и самой многочисленной группой инструментов — событийными профайлерами.




    Задача для тренировки


    В прошлой статье мы разбирали задачу 3 из Проекта Эйлера. Для разнообразия возьмём какой-нибудь другой пример, например, задачу 7 из этого же сборника задач:
    Выписав первые шесть простых чисел, получим 2, 3, 5, 7, 11 и 13. Очевидно, что 6-ое простое число — 13.
    Какое число является 10001-ым простым числом?

    Пишем код:
    """Project Euler problem 7 solve"""
    from __future__ import print_function
    import math
    import sys
    
    
    def is_prime(num):
        """Checks if num is prime number"""
        for i in range(2, int(math.sqrt(num)) + 1):
            if num % i == 0:
                return False
        return True
    
    
    def get_prime_numbers(count):
        """Get 'count' prime numbers"""
        prime_numbers = [2]
        next_number = 3
    
        while len(prime_numbers) < count:
            if is_prime(next_number):
                prime_numbers.append(next_number)
            next_number += 1
    
        return prime_numbers
    
    
    if __name__ == '__main__':
        try:
            count = int(sys.argv[1])
        except (TypeError, ValueError, IndexError):
            sys.exit("Usage: euler_7.py number")
        if count < 1:
            sys.exit("Error: number must be greater than zero")
    
        prime_numbers = get_prime_numbers(count)
        print("Answer: %d" % prime_numbers[-1])
    

    Помним, что код не идеален, и многие вещи можно сделать проще, лучше, быстрее. Именно в этом заключается цель данной статьи =)

    В прошлой статье я оконфузился и не сделал самого важного: тестов. На самом деле поломать программу в процессе рефакторинга или оптимизации легче простого, и каждый цикл профилирования и переписывания кода должен в обязательном порядке сопровождаться тестированием функционала (как непосредственно затронутого изменениями, так и всего остального, ведь сайд-эффекты такие сайд-эффекты). Попробуем исправиться и добавим тесты в нашу программу. Самый простой и подходящий для такого простого скрипта вариант — использовать модуль doctest. Добавляем тесты и запускаем их:
    Тесты
    """Project Euler problem 7 solve"""
    from __future__ import print_function
    import math
    import sys
    
    
    def is_prime(num):
        """
        Checks if num is prime number.
    
        >>> is_prime(2)
        True
        >>> is_prime(3)
        True
        >>> is_prime(4)
        False
        >>> is_prime(5)
        True
        >>> is_prime(41)
        True
        >>> is_prime(42)
        False
        >>> is_prime(43)
        True
        """
        for i in range(2, int(math.sqrt(num)) + 1):
            if num % i == 0:
                return False
        return True
    
    
    def get_prime_numbers(count):
        """
        Get 'count' prime numbers.
    
        >>> get_prime_numbers(1)
        [2]
        >>> get_prime_numbers(2)
        [2, 3]
        >>> get_prime_numbers(3)
        [2, 3, 5]
        >>> get_prime_numbers(6)
        [2, 3, 5, 7, 11, 13]
        >>> get_prime_numbers(9)
        [2, 3, 5, 7, 11, 13, 17, 19, 23]
        >>> get_prime_numbers(19)
        [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67]
        """
        prime_numbers = [2]
        next_number = 3
    
        while len(prime_numbers) < count:
            if is_prime(next_number):
                prime_numbers.append(next_number)
            next_number += 1
    
        return prime_numbers
    
    
    if __name__ == '__main__':
        try:
            count = int(sys.argv[1])
        except (TypeError, ValueError, IndexError):
            sys.exit("Usage: euler_7.py number")
        if count < 1:
            sys.exit("Error: number must be greater than zero")
    
        prime_numbers = get_prime_numbers(count)
        print("Answer: %d" % prime_numbers[-1])
    

    Запуск тестов и результат
    ➜ python -m doctest -v euler_7.py
    Trying:
        get_prime_numbers(1)
    Expecting:
        [2]
    ok
    Trying:
        get_prime_numbers(2)
    Expecting:
        [2, 3]
    ok
    Trying:
        get_prime_numbers(3)
    Expecting:
        [2, 3, 5]
    ok
    Trying:
        get_prime_numbers(6)
    Expecting:
        [2, 3, 5, 7, 11, 13]
    ok
    Trying:
        get_prime_numbers(9)
    Expecting:
        [2, 3, 5, 7, 11, 13, 17, 19, 23]
    ok
    Trying:
        get_prime_numbers(19)
    Expecting:
        [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67]
    ok
    Trying:
        is_prime(2)
    Expecting:
        True
    ok
    Trying:
        is_prime(3)
    Expecting:
        True
    ok
    Trying:
        is_prime(4)
    Expecting:
        False
    ok
    Trying:
        is_prime(5)
    Expecting:
        True
    ok
    Trying:
        is_prime(41)
    Expecting:
        True
    ok
    Trying:
        is_prime(42)
    Expecting:
        False
    ok
    Trying:
        is_prime(43)
    Expecting:
        True
    ok
    1 items had no tests:
        euler_7
    2 items passed all tests:
       6 tests in euler_7.get_prime_numbers
       7 tests in euler_7.is_prime
    13 tests in 3 items.
    13 passed and 0 failed.
    Test passed.
    


    Давайте посмотрим, насколько быстрый у нас получился код:
    ➜ python -m timeit -n 10 -s'import euler_7' 'euler_7.get_prime_numbers(10001)'
    10 loops, best of 3: 1.27 sec per loop
    

    Да, небыстро, есть что пооптимизировать =)

    Инструменты


    Стандартные библиотеки Python поражают своим разнообразием. В них, кажется, есть всё, что только может понадобится разработчику, и профайлеры не исключение. На самом деле их целых три «из коробки»:
    • cProfile — относительно новый (с версии 2.5) модуль, написанный на C и оттого быстрый
    • profile — нативная реализация профайлера (написан на чистом питоне), медленный, и поэтому не рекомендуется к использованию
    • hotshot — экспериментальный модуль на си, очень быстрый, но больше не поддерживается и в любой момент может быть удалён из стандартных библиотек


    cProfile


    Какой разговор о профилировании питона обходится без описания cProfile — одного из стандартных модулей Python?
    Уверен, каждый программист Python хоть раз пробовал запустить cProfile:
    ➜ python -m cProfile -s time euler_7.py 10001
             428978 function calls in 1.552 seconds
    
       Ordered by: internal time
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       104741    0.955    0.000    1.361    0.000 euler_7.py:7(is_prime)
       104741    0.367    0.000    0.367    0.000 {range}
            1    0.162    0.162    1.550    1.550 euler_7.py:32(get_prime_numbers)
       104741    0.039    0.000    0.039    0.000 {math.sqrt}
       104742    0.024    0.000    0.024    0.000 {len}
        10000    0.003    0.000    0.003    0.000 {method 'append' of 'list' objects}
            1    0.001    0.001    1.552    1.552 euler_7.py:1(<module>)
            1    0.000    0.000    0.000    0.000 {print}
            1    0.000    0.000    0.000    0.000 __future__.py:48(<module>)
            7    0.000    0.000    0.000    0.000 __future__.py:75(__init__)
            1    0.000    0.000    0.000    0.000 __future__.py:74(_Feature)
            1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    

    Сразу заметим разницу во времени выполнения программы: без профайлера: 1.27 секунды, с профайлером: 1.55 секунд, то есть на 20% медленнее в нашем конкретном случае. И это ещё очень хороший результат!

    Итак, мы видим, что самая долгая (по суммарному времени) операция — функция is_prime. Практически всё время программа проводит в этой функции. Следующий по «тяжести» вызов — функция range, которая как раз вызывается из функции is_prime. Читаем документацию и понимаем, что при вызове range в памяти создаётся список со всеми числами из заданного диапазона. С учётом того, что функция range вызывается 104741 раз, а верхняя граница диапазона при каждом вызове инкрементируется (перебираем числа последовательно), можно сделать вывод, что длина списка, создаваемого функцией range достигает сотни тысяч элементов к концу работы программы и список создаётся больше ста тысяч раз. Почитав ещё документацию мы узнаём, что нам следует использовать функцию xrange в этом цикле (внимательный читатель должен почувствовать сарказм в этом месте, ведь любой питонист знает про range VS xrange). Плюсом замены range на xrange будет так же явная экономия памяти (эту теорию мы проверим позже). Заменяем, запускаем тесты: всё ок, запускаем профайлер:
    ➜ python -m cProfile -s time euler_7.py 10001
             324237 function calls in 1.010 seconds
    
       Ordered by: internal time
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       104741    0.825    0.000    0.857    0.000 euler_7.py:7(is_prime)
            1    0.127    0.127    1.009    1.009 euler_7.py:32(get_prime_numbers)
       104741    0.032    0.000    0.032    0.000 {math.sqrt}
       104742    0.022    0.000    0.022    0.000 {len}
        10000    0.003    0.000    0.003    0.000 {method 'append' of 'list' objects}
            1    0.001    0.001    1.010    1.010 euler_7.py:1(<module>)
            1    0.000    0.000    0.000    0.000 {print}
            1    0.000    0.000    0.000    0.000 __future__.py:48(<module>)
            7    0.000    0.000    0.000    0.000 __future__.py:75(__init__)
            1    0.000    0.000    0.000    0.000 __future__.py:74(_Feature)
            1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    

    1.010 секунды вместо 1.552, то есть в полтора раза быстрее! Нормально. Теперь самое узкое место в программе — функция is_prime сама по себе. Оптимизируем её позже, с использованием других инструментов.

    Выводить результаты профилирования в консоль не всегда удобно, гораздо удобнее сохранять их в файл для дальнейшего анализа. Для этого можно воспользоваться ключом "-o":
    ➜ python -m cProfile -o euler_7.prof euler_7.py 10001
    ➜ ls
    euler_7.prof      euler_7.py
    

    или можно воспользоваться простейшим декоратором:
    import cProfile
    
    
    def profile(func):
        """Decorator for run function profile"""
        def wrapper(*args, **kwargs):
            profile_filename = func.__name__ + '.prof'
            profiler = cProfile.Profile()
            result = profiler.runcall(func, *args, **kwargs)
            profiler.dump_stats(profile_filename)
            return result
        return wrapper
    
    
    @profile
    def get_prime_numbers(count):
        ...
    

    И тогда при каждом вызове функции foo будет сохранён файл с результатами профилирования («get_prime_numbers.prof» в нашем случае).

    hotshot


    hotshot — ещё один стандартный модуль Python, на данный момент не поддерживается и в любое время может быть удалён из стандартных библиотек. Но пока он есть, можно использовать его, ведь он очень быстрый и даёт минимальный оверхед при запуске программы под профайлером. Использовать его очень просто:
    import hotshot
    
    
    prof = hotshot.Profile("profile_name.prof")
    prof.start()
    
    # your code goes here
    
    prof.stop()
    prof.close()
    

    Или в виде декоратора:
    import hotshot
    
    
    def profile(func):
        """Decorator for run function profile"""
        def wrapper(*args, **kwargs):
            profile_filename = func.__name__ + '.prof'
            profiler = hotshot.Profile(profile_filename)
            profiler.start()
            result = func(*args, **kwargs)
            profiler.stop()
            profiler.close()
            return result
        return wrapper
    
    
    @profile
    def get_prime_numbers(count):
        ...
    


    Анализ результатов профилирования


    Редко когда получается вывести результаты профилирования на экран сразу после запуска программы. Да и смысла в таких результатах немного: только простейшие скрипты и удастся изучить. Для просмотра и анализа данных лучше воспользоваться встроенным в Python модулем pstats (удобнее в сочетании с замечательной консолью iPython):
    ➜ ipython
    
    In [1]: import pstats
    
    In [2]: p = pstats.Stats('get_prime_numbers.prof')
    
    In [3]: p.sort_stats('calls').print_stats()
    
             324226 function calls in 1.018 seconds
    
       Ordered by: call count
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       104742    0.023    0.000    0.023    0.000 {len}
       104741    0.821    0.000    0.854    0.000 euler_7.py:19(is_prime)
       104741    0.034    0.000    0.034    0.000 {math.sqrt}
        10000    0.003    0.000    0.003    0.000 {method 'append' of 'list' objects}
            1    0.138    0.138    1.018    1.018 euler_7.py:44(get_prime_numbers)
    

    Консоль, конечно, хорошая штука, но не очень наглядная. В особо сложных ситуациях, в программах с сотнями и тысячами вызовов анализ результатов представляется затруднительным. Так уж устроены люди, что графическую информацию нам (в отличие от компьютеров) воспринимать гораздо проще, чем текстовую. На помощь приходят различные инструменты для анализа результатов профилирования.

    kcachegrind


    Начну, пожалуй, с такого известного инструмента, как kcachegrind, который, на самом деле, предназначен для визуализации результатов утилиты Callgrind, но переконвертировав результаты Python-профалера, их можно открыть в kcachegrind. Конвертирование выполняется с помощью утилиты pyprof2calltree:
    ➜ pip install pyprof2calltree
    ➜ pyprof2calltree -i profile_results.prof -o profile_results.kgrind
    

    Можно сразу открыть результаты в kcachegrind, без сохранения в файл:
    ➜ pyprof2calltree -i profile_results.prof -k
    

    Программа позволяет наглядно увидеть сколько времени занимает тот или иной вызов, а так же все вызовы внутри него. Так же можно посмотреть дерево вызовов и кучу другой полезной информации. Интерфейс выглядит таким образом:


    RunSnakeRun


    Ещё одна программа для визуализации результатов профайлинга RunSnakeRun была изначально написана для работы с профайлером питона (это видно из её названия). Похожа на kcachegrind, но, как говорят авторы, выгодно отличается более простым интерфейсом и функционалом. Установка не вызовет сложностей:
    ➜ brew install wxwidgets
    ➜ pip install SquareMap RunSnakeRun
    

    Использование тоже:
    ➜ runsnake profile_results.prof
    

    Точно так же видим карту квадратов: чем больше площадь квадрата, тем больше времени заняло выполнение соответствующей функции:


    RunSnakeRun позволяет так же визуализировать результат профилирования потребления памяти с помощью утилиты Meliae:


    gprof2dot


    Утилита gprof2dot генерирует картинку с деревом вызовов функций и информацией о времени их выполнения. В большинстве случаев этого достаточно для поиска узких мест в программе. Ставим и генерируем картинку:
    ➜ brew install graphviz
    ➜ pip install gprof2dot
    ➜ gprof2dot -f pstats profile_results.prof | dot -Tpng -o profile_results.png
    

    Результат:


    Профилирование Django


    Для профилирования Django удобно использовать модуль django-extensions, который, помимо кучи разных полезных вещей, имеет полезную команду «runprofileserver». Использовать его просто. Ставим:
    ➜ pip install django-extensions

    Добавляем application в settings.py:
    INSTALLED_APPS += ('django_extensions',)
    

    Запускаем:
    ➜ python manage.py runprofileserver --use-cprofile --prof-path=/tmp/prof/
    

    В директории /tmp/prof/ будет создан файл с результатами профилирования для каждого запроса в приложение:
    ➜ ls /tmp/prof/
    admin.000276ms.1374075009.prof
    admin.account.user.000278ms.1374075014.prof admin.jsi18n.000185ms.1374075018.prof
    favicon.ico.000017ms.1374075001.prof
    root.000073ms.1374075004.prof
    static.admin.css.base.css.000011ms.1374075010.prof
    static.admin.css.forms.css.000013ms.1374075017.prof
    static.admin.img.icon-yes.gif.000001ms.1374075015.prof
    static.admin.img.sorting-icons.gif.000001ms.1374075015.prof
    static.admin.js.core.js.000018ms.1374075014.prof
    static.admin.js.jquery.js.000003ms.1374075014.prof
    static.css.bootstrap-2.3.2.min.css.000061ms.1374074996.prof
    static.img.glyphicons-halflings.png.000001ms.1374075005.prof
    static.js.bootstrap-2.3.2.min.js.000004ms.1374074996.prof
    static.js.jquery-2.0.2.min.js.000001ms.1374074996.prof
    user.login.000187ms.1374075001.prof
    

    Дальнейший анализ можно выполнить с помощью любого из инструментов, перечисленных выше: pstats, kcachegrind, RunSnakeRun или gprof2dot. Или любого другого =)

    Помимо встроенного в Python профайлера имеется так же масса сторонних программ, простых и сложных, полезных и не очень.

    pycallgraph


    pycallgraph позволяет строить дерево вызовов программы Python. Ставим:
    ➜ brew install graphviz
    ➜ pip install pycallgraph
    

    Запускаем и смотрим результат:
    ➜ pycallgraph graphviz -- euler_7.py 10001
    ➜ open pycallgraph.png
    



    line_profiler


    line_profiler, как следует из его названия, позволяет построчно отпрофилировать нужные участки кода. Ставим:
    ➜ pip install line_profiler
    

    Добавляем в нужные места декоратор «profile» (я временно убрал докстринги для более компактного вывода результатов):
    @profile
    def is_prime(num):
        for i in xrange(2, int(math.sqrt(num)) + 1):
            if num % i == 0:
                return False
        return True
    
    
    @profile
    def get_prime_numbers(count):
        prime_numbers = [2]
        next_number = 3
    
        while len(prime_numbers) < count:
            if is_prime(next_number):
                prime_numbers.append(next_number)
            next_number += 1
    
        return prime_numbers
    

    Запускаем профилирование:
    ➜ kernprof.py -v -l euler_7.py 10001
    Wrote profile results to euler_7.py.lprof
    Timer unit: 1e-06 s
    
    File: euler_7.py
    Function: is_prime at line 7
    Total time: 10.7963 s
    
    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         7                                           @profile
         8                                           def is_prime(num):
         9   2935963      5187211      1.8     48.0      for i in xrange(2, int(math.sqrt(num)) + 1):
        10   2925963      5421919      1.9     50.2          if num % i == 0:
        11     94741       169309      1.8      1.6              return False
        12     10000        17904      1.8      0.2      return True
    
    File: euler_7.py
    Function: get_prime_numbers at line 15
    Total time: 23.263 s
    
    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
        15                                           @profile
        16                                           def get_prime_numbers(count):
        17         1            5      5.0      0.0      prime_numbers = [2]
        18         1            3      3.0      0.0      next_number = 3
        19
        20    104742       208985      2.0      0.9      while len(prime_numbers) < count:
        21    104741     22843717    218.1     98.2          if is_prime(next_number):
        22     10000        22405      2.2      0.1              prime_numbers.append(next_number)
        23    104741       187927      1.8      0.8          next_number += 1
        24
        25         1            2      2.0      0.0      return prime_numbers
    

    Сразу замечаем огромный оверхед: программа выполнялась больше 30 секунд, при том, что без профайлера она отрабатывает быстрее, чем за секунду.

    Анализируя результаты можно сделать вывод, что наибольшее время программа тратит в строках 9 и 10, проверяя делители числа для определения его «простоты». И для каждого последующего числа происходят все те же самые проверки. Логичной оптимизацией программы является проверка в качестве делителей только тех чисел, которые ранее были определены как простые:
    def is_prime(num, prime_numbers):
        """
        Checks if num is prime number.
    
        >>> is_prime(2, [])
        True
        >>> is_prime(3, [2])
        True
        >>> is_prime(4, [2, 3])
        False
        >>> is_prime(5, [2, 3])
        True
        >>> is_prime(41, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37])
        True
        >>> is_prime(42, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41])
        False
        >>> is_prime(43, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41])
        True
        """
        limit = int(math.sqrt(num)) + 1
        for i in prime_numbers:
            if i > limit:
                break
            if num % i == 0:
                return False
        return True
    
    
    def get_prime_numbers(count):
        """
        Get 'count' prime numbers.
    
        >>> get_prime_numbers(1)
        [2]
        >>> get_prime_numbers(2)
        [2, 3]
        >>> get_prime_numbers(3)
        [2, 3, 5]
        >>> get_prime_numbers(6)
        [2, 3, 5, 7, 11, 13]
        >>> get_prime_numbers(9)
        [2, 3, 5, 7, 11, 13, 17, 19, 23]
        >>> get_prime_numbers(19)
        [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67]
        """
        prime_numbers = [2]
        next_number = 3
    
        while len(prime_numbers) < count:
            if is_prime(next_number, prime_numbers):
                prime_numbers.append(next_number)
            next_number += 1
    
        return prime_numbers
    

    Запускаем тесты, убеждаемся, что всё отрабатывает правильно, замерим время выполнения программы:
    ➜ python -m timeit -n 10 -s'import euler_7' 'euler_7.get_prime_numbers(10001)'
    10 loops, best of 3: 390 msec per loop
    

    Ускорили работу почти в три раза, неплохо. Запустим ещё разок профилирование:
    ➜ kernprof.py -v -l euler_7.py 10001
    Wrote profile results to euler_7.py.lprof
    Timer unit: 1e-06 s
    
    File: euler_7.py
    Function: is_prime at line 7
    Total time: 4.54317 s
    
    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         7                                           @profile
         8                                           def is_prime(num, prime_numbers):
         9    104741       310160      3.0      6.8      limit = int(math.sqrt(num)) + 1
        10    800694      1296045      1.6     28.5      for i in prime_numbers:
        11    800692      1327770      1.7     29.2          if i > limit:
        12      9998        17109      1.7      0.4              break
        13    790694      1409731      1.8     31.0          if num % i == 0:
        14     94741       165761      1.7      3.6              return False
        15     10000        16599      1.7      0.4      return True
    
    File: euler_7.py
    Function: get_prime_numbers at line 18
    Total time: 10.5464 s
    
    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
        18                                           @profile
        19                                           def get_prime_numbers(count):
        20         1            4      4.0      0.0      prime_numbers = [2]
        21         1            2      2.0      0.0      next_number = 3
        22
        23    104742       202443      1.9      1.9      while len(prime_numbers) < count:
        24    104741     10143489     96.8     96.2          if is_prime(next_number, prime_numbers):
        25     10000        22374      2.2      0.2              prime_numbers.append(next_number)
        26    104741       178074      1.7      1.7          next_number += 1
        27
        28         1            1      1.0      0.0      return prime_numbers
    

    Как видим, программа стала выполняться гораздо быстрее.

    memory_profiler


    Для профилирования памяти можно использовать memory_profiler. Использовать его так же просто, как line_profiler. Ставим:
    ➜ pip install psutil memory_profiler
    

    Запускаем:
    ➜ python -m memory_profiler euler_7.py 10001
    Filename: euler_7.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
        18    8.441 MiB   -0.531 MiB   @profile
        19                             def get_prime_numbers(count):
        20    8.445 MiB    0.004 MiB       prime_numbers = [2]
        21    8.445 MiB    0.000 MiB       next_number = 3
        22
        23    8.973 MiB    0.527 MiB       while len(prime_numbers) < count:
        24                                     if is_prime(next_number, prime_numbers):
        25    8.973 MiB    0.000 MiB               prime_numbers.append(next_number)
        26    8.973 MiB    0.000 MiB           next_number += 1
        27
        28    8.973 MiB    0.000 MiB       return prime_numbers
    
    
    Filename: euler_7.py
    
    Line #    Mem usage    Increment   Line Contents
    ================================================
         7    8.973 MiB    0.000 MiB   @profile
         8                             def is_prime(num, prime_numbers):
         9    8.973 MiB    0.000 MiB       limit = int(math.sqrt(num)) + 1
        10    8.973 MiB    0.000 MiB       for i in prime_numbers:
        11    8.973 MiB    0.000 MiB           if i > limit:
        12    8.973 MiB    0.000 MiB               break
        13    8.973 MiB    0.000 MiB           if num % i == 0:
        14    8.973 MiB    0.000 MiB               return False
        15    8.973 MiB    0.000 MiB       return True
    

    Как видим, никаких особых проблем с памятью у нас нет. Всё в пределах нормы.

    Ещё инструменты


    Перечислю ещё несколько инструментов для профилирования, к сожалению, статья и так получилась огромной, и разобрать все из них не предоставляется возможным.
    • Dowser — отличнейший инструмент для профилирования памяти
    • guppy — ещё одна программа для профилирования памяти
    • Meliae — уже упоминавшийся ранее инструмент для профилирования памяти, которую можно использовать вместе с RunSnakeRun
    • muppy — обнаружение утечек памяти
    • memprof — снова профилирование памяти
    • objgraph — интересный инструмент для исследования объектов


    Мы познакомились с инструментами для профилирования кода на Python. Множество из них осталось за кадром, надеюсь, мои коллеги дополнят меня в комментариях.

    В следующей статье мы познакомимся с методами и инструментами для отладки Python-программ. Оставайтесь на связи!

    Минутка статистики: в трёх статьях про профилирование питона я использовал слово «профилирование» больше ста раз.
    Метки:
    • +66
    • 41k
    • 9
    Mail.Ru Group 1 003,66
    Строим Интернет
    Поделиться публикацией
    Похожие публикации
    Комментарии 9
    • +1
      Отличная статья. Но скриншоты не смотрибельны без увеличения. Хорошо, хоть ссылки на продукты есть, можно у них посмотреть.
      • 0
        Спасибо!
        Согласен, со скриншотами не очень хорошо получилось. Попробую перезалить со ссылкой на полноценную картинку, если успею.
      • +1
        Видео и слайды с выступления Dreadatour про профилирование на Moscowdjango. Клёво, что теперь есть и серия постов!
        • 0
          Да, я давал ссылку на эту страницу в самом начале первой статьи.
        • 0
          Планируется ли обзор инструментов для интерактивной отладки (pdb, PyDev, PyCharm и пр. )?
          • 0
            В следующей статье мы познакомимся с методами и инструментами для отладки Python-программ.
            • +1
              Хм. сорри.
              Еще будет очень интерестно почитать о стандартном модуле faulthandler :)
              • 0
                Хм, это не совсем отладка, да и область применения его, на мой взгляд, достаточно узкая.

                Не хочешь сам об этом написать? ;))
          • 0
            Мне кажется не лишним будет и этот продукт: https://habrahabr.ru/post/276607/ Питон там тоже добавили.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое