Русский
Русский
English
Статистика
Реклама

Перевод Визуализируйте многопоточные программы Python с open source инструментом VizTracer

Специально к старту нового потока курса Fullstack-разработчик на Python, представляем небольшой авторский обзор кроссплатформенного инструмента визуализации многопоточных программ VizTracer. У VizTracer 57 форков и 841 звезд на Github. Настраиваемые события, отчёты в HTML, детальная информация о функциях с их исходным кодом, простота применения, отсутствие зависимостей и малый оверхед превращают VizTracer в мастхэв Python-разработчика.


Конкурентность важная часть современного программирования, когда у нас несколько ядер и много задач, которые должны кооперироваться. Однако, когда многопоточные программы не выполняются последовательно, понять их трудно. Инженерам не так-то просто идентифицировать баги и проблемы производительности в таких программах, как это делается в однопоточных программах с одной задачей.

В Python вариантов конкурентности много. Самый распространённый, вероятно, многопоточность, которой добиваются с помощью модуля threading и несколько процессов с помощью модулей myltiprocessing и subprocess, а также недавно появившийся способ async из модуля asyncio. До появления VizTracer в инструментах, использующих эти техники в целях анализа программ, был пробел.

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

Попробуем с простой задачей

Определим, являются ли числа в массиве простыми, и вернём массив логических значений. Вот простое решение:

def is_prime(n):    for i in range(2, n):        if n % i == 0:            return False    return Truedef get_prime_arr(arr):    return [is_prime(elem) for elem in arr]

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

if __name__ == "__main__":    num_arr = [random.randint(100, 10000) for _ in range(6000)]    get_prime_arr(num_arr)

Выполняем команду:

viz_tracer my_program.py

Отчёт стека вызовов показывает, что выполнение кода заняло 140 мс, а большую часть времени заняло выполнение get_prime_arr.

Здесь на каждом элементе массива выполняется функция is_prime. Это ожидаемо и не интересно, если VizTracer вам знаком.

Теперь попробуем многопоточную программу

Сделаем то же самое в программе с несколькими потоками:

if __name__ == "__main__":    num_arr = [random.randint(100, 10000) for i in range(2000)]    thread1 = Thread(target=get_prime_arr, args=(num_arr,))    thread2 = Thread(target=get_prime_arr, args=(num_arr,))    thread3 = Thread(target=get_prime_arr, args=(num_arr,))    thread1.start()    thread2.start()    thread3.start()    thread1.join()    thread2.join()    thread3.join()

Чтобы соответствовать рабочей нагрузке однопоточной программы, в коде прописан массив в 2000 элементов на три потока и моделируется ситуация, когда все эти потоки совместно работают над задачей.

Если вы знакомы с Python Global Lock (GIL), то можете ожидать, что программа не станет быстрее: из-за оверхеда она выполняется немного дольше 140 мс, однако можно наблюдать конкурентность множества потоков:

Когда работал один поток (и много раз выполнял функции is_prime), другой поток был заморожен (одна функция is_prime). Позже они поменялись. Это произошло из-за GIL, и причина в том, что в Python нет настоящей многопоточности. Возможна конкурентность, но не параллелизм.

Попробуем поработать с multiprocessing

Чтобы достичь параллелизма, воспользуемся библиотекой multiprocessing. Вот новая версия кода с применением этой библиотеки:

if __name__ == "__main__":    num_arr = [random.randint(100, 10000) for _ in range(2000)]       p1 = Process(target=get_prime_arr, args=(num_arr,))    p2 = Process(target=get_prime_arr, args=(num_arr,))    p3 = Process(target=get_prime_arr, args=(num_arr,))    p1.start()    p2.start()    p3.start()    p1.join()    p2.join()    p3.join()

Чтобы запустить его с помощью VizTracer, понадобится дополнительный аргумент:

viztracer --log_multiprocess my_program.py

Вся программа занимает чуть больше 50 мс, задача завершается менее чем за те же 50 мс, то есть программа ускорилась приблизительно втрое. Чтобы сравнить производительность, напишем версию в несколько процессов:

Без GIL множество процессов достигают параллелизма, то есть множество функций is_prime выполняются параллельно.

Однако многопоточность в Python не бесполезна. Например, для вычислительно-интенсивных программ и программ с интенсивным вводом-выводом, при помощи sleep можно имитировать задержку ввода-вывода:

def io_task():    time.sleep(0.01)

Попробуем в однопоточной программе с одной задачей:

if __name__ == "__main__":    for _ in range(3):        io_task()

.

Вся программа занимает 30 мс; ничего особенного. Теперь воспользуемся многопоточностью:

if __name__ == "__main__":    thread1 = Thread(target=io_task)    thread2 = Thread(target=io_task)    thread3 = Thread(target=io_task)    thread1.start()    thread2.start()    thread3.start()    thread1.join()    thread2.join()    thread3.join()

Выполнение занимает 10 мс: ясно, что все потоки работали одновременно и повысили производительность.

Теперь поработаем с asyncio

В Python попробовали ввести интересную функциональность асинхронное программирование. Нашу задачу можно написать асинхронно:

import asyncioasync def io_task():    await asyncio.sleep(0.01)async def main():    t1 = asyncio.create_task(io_task())    t2 = asyncio.create_task(io_task())    t3 = asyncio.create_task(io_task())    await t1    await t2    await t3if __name__ == "__main__":    asyncio.run(main())

Поскольку asyncio буквально однопоточный планировщик с задачами, вы можете использовать VizTracer вместе с этим планировщиком:

Всё те же 10 мс, но большинство отображаемых функций нижележащая структура, которая, вероятно, не интересует пользователей. Чтобы решить эту проблему, можно использовать --log_async флаг разделит задачу на части:

viztracer --log_async my_program.py

Теперь задачи пользователя намного яснее. Большую часть времени выполняемых задач нет: единственное, что делает программа, спит. Вот интересная часть:

На временной шкале показывается, когда задачи создавались и выполнялись. Task-1 была сопрограммой mail(), которая создаёт другие задачи. Задачи 2, 3 и 4 выполняли io_task и sleep, а потом ждали пробуждения. Как показывает график, задачи не перекрывают друг друга, поскольку программа однопоточная, и VizTracer визуализировал её так, чтобы она была понятной. Чтобы сделать код интереснее, добавим вызов time.sleep, чтобы заблокировать асинхронный цикл:

async def io_task():    time.sleep(0.01)    await asyncio.sleep(0.01)

Программа заняла намного больше времени (40 мс) и задачи заняли свои места в асинхронном планировщике. Это поведение очень полезно для диагностики проблем поведения и производительности в асинхронных программах.

Смотрите на происходящее с помощью VizTracer

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

Исходный код VizTracer открыт под лицензией Apache 2.0, поддерживаются все операционные системы Linux , MacOS, Windows. Вы можете узнать больше о его функциях и увидеть исходный код в репозитории VizTracer на Github. А освоить разработку на Python и стать Fullstack-разработчиком на нашем специализированном курсе.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Источник: habr.com
К списку статей
Опубликовано: 15.03.2021 14:07:31
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании skillfactory

Open source

Python

Параллельное программирование

Skillfactory

Программирование

Opensourse

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru