Солнце говорит: определи функцию-бутылочное горлышко с помощью Python cProfile!
Мы!
У-у!
Приблизьтесь!
Мяу!
Здравствуйте, я бездарь.
В последнее время мне очень нравится слушать канал Масахиро Сакураи, бывшего сотрудника HAL Laboratory, в фоновом режиме, пока я вожусь с компьютером, как будто это радио.
На самом деле, вспомнились времена, когда я играл на 3DS, и Nintendo Direct от президента Ивата, которые я смотрел тогда, всегда были веселыми. Пересматривая это, я вижу, что такие люди, как Тосио Икеда из Fujitsu, обладают огромной энергией в документальных фильмах, и это был невероятный мир, когда они неоднократно ездили в США в то время. В то время шла Вьетнамская война, и, должно быть, оставались тени послевоенного периода после Второй мировой войны, но откуда взялась вся эта энергия?
Введение
Я не особо задумывался о скорости выполнения скриптовых языков, но когда я запускал свой cuckooget, это меня заинтриговало.
Часть кода, написанная на Rust, может быть выполнена внутри интерпретатора с помощью PyO3/maturin.
Проще говоря, функции, написанные на Rust, превращаются в библиотеки Python и выполняются через скомпилированный бинарный файл кода Rust. Простая причина в том, что для процессов, требующих вычислений CPU, выполнение через этот бинарный файл, вероятно, будет быстрее. В итоге это стало быстрее, но в этом есть большая ошибка.
Во-первых, измерения проводились без предварительного бенчмаркинга кода, написанного исключительно на Python.
Даже в «Заметках о программировании на C», написанных Робом Пайком, которого называют отцом Go, говорится:
Правило 1: Вы не можете знать, где программа будет тратить время. Узкие места возникают в неожиданных местах. Поэтому не делайте предположений и не занимайтесь «скоростными хаками», пока не выясните, где находится узкое место.
Если так, то измерения следует проводить. Если это принцип для низкоуровневого языка C, то не может быть, чтобы он не применялся к более высоким уровням.
Я часто забываю такие вещи, поэтому постоянно чувствую необходимость в чем-то вроде философии UNIX для самодисциплины, чтобы регулярно проверять себя.
Так я написал, но я человек, и хотя я это понимаю, я склонен к выбросам...
Измерение с помощью cProfile
Сначала я использовал примитивный метод: import time, start_time = time.time(), а после завершения выполнения функции end_time = time.time() и print(f"exec time {start_time - end_time}"). Но это так неэффективно, когда используешь компьютер! Поэтому я поискал что-то, что могло бы показывать время выполнения для каждой функции.
Оказывается, существует стандартная библиотека под названием cProfile.
Следующий сайт очень понятен:
Анализ производительности кода Python с помощью cProfile
В моем случае я проверял следующим образом:
python3 -m cProfile -o profile.pstats ./main.py https://soulminingrig.com/ site
Таким образом, время выполнения функций, запускаемых с помощью ./main.py , сохраняется в бинарном файле profile.pstats.
Проверка результатов выполнения функций
На предыдущем сайте это делалось с использованием исполняемого файла Python, но гораздо проще войти в режим командной строки pstats и проверить.
python3 -m pstats profile.pstats
Теперь, войдя в режим командной строки pstats, проверьте результат вывода следующим образом:
profile.pstats% stats 5
Thu Nov 7 20:24:10 2024 profile.pstats
48488696 function calls (47625490 primitive calls) in 51.151 seconds
Random listing order was used
List reduced from 3017 to 5 due to restriction <5>
ncalls tottime percall cumtime percall filename:lineno(function)
1586 0.001 0.000 0.001 0.000 /home/haturatu/.local/lib/python3.12/site-packages/aiohttp/client_reqrep.py:909(__del__)
80 0.000 0.000 0.001 0.000 /usr/lib/python3.12/copy.py:247(_reconstruct)
4 0.000 0.000 0.005 0.001 /usr/lib/python3.12/encodings/__init__.py:71(search_function)
3 0.000 0.000 98.745 32.915 /usr/lib/python3.12/asyncio/base_events.py:651(run_until_complete)
1103 0.001 0.000 0.003 0.000 /usr/lib/python3.12/re/_parser.py:312(_class_escape)
Другие команды можно проверить с помощью help.
profile.pstats% help
Documented commands (type help <topic>):
========================================
EOF add callees callers help quit read reverse sort stats strip
В моем случае я вывел stats 100, открыл его в vim из другого терминала, скопировал и вставил, а затем вышел с помощью quit.
Затем я просматриваю этот текстовый файл, сохраненный после вставки в vim, используя awk и sort.
В случае tottime
$ awk '{ print $2","$6 }' bench | sort | column -t -s "," | tail
0.009 /usr/lib/python3.12/re/_compiler.py:243(_optimize_charset)
0.010 /usr/lib/python3.12/concurrent/futures/_base.py:428(result)
0.012 /usr/lib/python3.12/asyncio/base_events.py:627(run_forever)
0.013 /usr/lib/python3.12/re/__init__.py:280(_compile)
0.018 /usr/lib/python3.12/re/_compiler.py:37(_compile)
0.022 /usr/lib/python3.12/concurrent/futures/_base.py:497(set_running_or_notify_cancel)
0.030 /home/haturatu/git/devgit/async_web_mirror.py:186(download_and_save_image)
0.031 /usr/lib/python3.12/re/_parser.py:512(_parse)
0.061 /usr/lib/python3.12/concurrent/futures/_base.py:537(set_result)
tottime filename:lineno(function)
Кажется, что tottime само по себе достаточно для бенчмаркинга каждой функции.
Бутылочное горлышко Beautiful Soup 4 — это просто мое воображение?
В другой среде медленным оказался search_tag из element.py, вызываемый из bs4, который, казалось, замедлял работу.
Меня заинтересовало это место:
if ((not self.name)
or call_function_with_tag_data
or (markup and self._matches(markup, self.name))
or (not markup and self._matches(markup_name, self.name))):
Трудно поверить, что оператор or является узким местом...
Однако в этом element.py есть только 7 мест, где используется or для оценки, и три из них используются здесь.
Что-то здесь не так.
Итак, поскольку тема перешла к bs4, на этом пока все.
Снова жду вашего сотрудничества.