Опубликован: 22.12.2005 | Доступ: свободный | Студентов: 24084 / 1831 | Оценка: 4.18 / 3.71 | Длительность: 16:10:00
ISBN: 978-5-9556-0109-0
Лекция 14:

Устройство интерпретатора языка Python

< Лекция 13 || Лекция 14: 12345678

Профайлер

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

Модуль profile

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

В качестве примера можно рассмотреть профилирование функции для поиска строк из списка, наиболее похожих на данную. Для того чтобы качественно профилировать функцию difflib.get_close_matches(), нужен большой объем данных. В файле russian.txt собрано 160 тысяч слов русского языка. Следующая программа поможет профилировать функцию difflib.get_close_matches():

import difflib, profile

                def print_close_matches(word):
                  print "\n".join(difflib.get_close_matches(word + "\n", open("russian.txt")))

                profile.run(r'print_close_matches("профайлер")')

При запуске этой программы будет выдано примерно следующее:

провайдер

 трайлер

 бройлер

 899769 function calls (877642 primitive calls) in 23.620 CPU seconds

 Ordered by: standard name

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 1    0.000    0.000   23.610   23.610 <string>:1(?)
 1    0.000    0.000   23.610   23.610 T.py:6(print_close_matches)
 1    0.000    0.000    0.000    0.000 difflib.py:147(__init__)
 1    0.000    0.000    0.000    0.000 difflib.py:210(set_seqs)
 159443    1.420    0.000    1.420    0.000 difflib.py:222(set_seq1)
 2    0.000    0.000    0.000    0.000 difflib.py:248(set_seq2)
 2    0.000    0.000    0.000    0.000 difflib.py:293(__chain_b)
 324261    2.240    0.000    2.240    0.000 difflib.py:32(_calculate_ratio)
 28317    1.590    0.000    1.590    0.000 difflib.py:344(find_longest_match)
 6474    0.100    0.000    2.690    0.000 difflib.py:454(get_matching_blocks)
 28317/6190    1.000    0.000    2.590    0.000 difflib.py:480(__helper)
 6474    0.450    0.000    3.480    0.001 difflib.py:595(ratio)
 28686    0.240    0.000    0.240    0.000 difflib.py:617(<lambda>)
 158345    8.690    0.000    9.760    0.000 difflib.py:621(quick_ratio)
 159442    2.950    0.000    4.020    0.000 difflib.py:650(real_quick_ratio)
 1    4.930    4.930   23.610   23.610 difflib.py:662(get_close_matches)
 1    0.010    0.010   23.620   23.620 profile:0(print_close_matches("профайлер"))
 0    0.000             0.000          profile:0(profiler)

Здесь колонки таблицы показывают следующие значения: ncalls - количество вызовов (функции), tottime - время выполнения кода функции (не включая времени выполнения вызываемых из нее функций), percall - то же время, в пересчете на один вызов, cumtime - суммарное время выполнения функции (и всех вызываемых из нее функций), filename - имя файла, lineno - номер строки в файле, function - имя функции (если эти параметры известны).

Из приведенной статистики следует, что наибольшие усилия по оптимизации кода необходимо приложить в функциях quick_ratio() (на нее потрачено 8,69 секунд), get_close_matches() (4,93 секунд), затем можно заняться real_quick_ratio() (2,95 секунд) и _calculate_ratio() (секунд).

Это лишь самый простой вариант использования профайлера: модуль profile (и связанный с ним pstats ) позволяет получать и обрабатывать статистику: их применение описано в документации.

Модуль timeit

Предположим, что проводится оптимизация небольшого участка кода. Необходимо определить, какой из вариантов кода является наиболее быстрым. Это можно сделать с помощью модуля timeit.

В следующей программе используется метод timeit() для измерения времени, необходимого для вычисления небольшого фрагмента кода. Измерения проводятся для трех вариантов кода, делающих одно и то же: конкатенирующих десять тысяч строк в одну строку. В первом случае используется наиболее естественный, "лобовой" прием инкрементной конкатенации, во втором - накопление строк в списке с последующим объединением в одну строку, в третьем применяется списковое включение, а затем объединение элементов списка в одну строку:

from timeit import Timer

                t = Timer("""
                res = ""
                for k in range(1000000,1010000):
                  res += str(k)
                """)
                print t.timeit(200)

                t = Timer("""
                res = []
                for k in range(1000000,1010000):
                  res.append(str(k))
                res = ",".join(res)
                """)
                print t.timeit(200)

                t = Timer("""
                res = ",".join([str(k) for k in range(1000000,1010000)])
                """)
                print t.timeit(200)

Разные версии Python дадут различные результаты прогонов:

# Python 2.3
                77.6665899754
                10.1372740269
                9.07727599144

                # Python 2.4
                9.26631307602
                9.8416929245
                7.36629199982

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

Если требуются более точные результаты, рекомендуется использовать метод repeat(n, k) - он позволяет вызывать timeit(k) n раз, возвращая список из n значений. Необходимо отметить, что на результаты может влиять загруженность компьютера, на котором проводятся испытания.

< Лекция 13 || Лекция 14: 12345678
Сергей Крупко
Сергей Крупко

Добрый день.

Я сейчас прохожу курс  повышения квалификации  - "Профессиональное веб-программирование". Мне нужно получить диплом по этому курсу. Я так полагаю нужно его оплатить чтобы получить диплом о повышении квалификации. Как мне оплатить этот курс?

 

Павел Ялганов
Павел Ялганов

Скажите экзамен тоже будет ввиде теста? или там будет какое то практическое интересное задание?