> 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(
> 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, инкрементная конкатенация была оптимизирована и теперь имеет даже лучший результат, чем версия со списками (которая вдобавок требует больше памяти). Но чемпионом все–таки является работа со списковым включением, поэтому свертывание циклов в списковое включение позволяет повысить эффективность кода.