Рубрики
Без рубрики

” MultiStreaded Python: Slithering через узкое место ввода-вывода »

Недавно я разработал проект, который я позвонил Hydra: многоподобная проверка ссылок, написанная в Python …. Теги с информатикой, программированием, Python.

Я недавно разработал проект, который я позвонил Гидра : Многопоточная проверка ссылок, написанная в Python. В отличие от многих соседних сайтов Python, я обнаружил, что при исследовании Hydra использует только стандартные библиотеки без внешних зависимостей, таких как BeautifulSoup. Предполагается, что он будет запускаться как часть процесса CI/CD, поэтому часть его успеха зависела от быстрой.

Несколько потоков в Python – это немного отвратительного предмета (не извините) в том, что интерпретатор Python на самом деле не позволяет выполнять несколько потоков одновременно. Питона Блокировка глобального переводчика или GIL, предотвращает несколько потоков из выполнения байтокодов Python сразу. Каждый поток, который хочет выполнить, должен сначала дождаться выпуске GIL на текущей выполнении потока. Гил в значительной степени микрофон на неблагодарной конференц-панели, за исключением того, где никто не доходит до крика.

Это имеет преимущество предотвращения Гоночные условия Отказ Это, однако, не хватает преимуществ производительности, предоставляемых, используя несколько задач параллельно. (Если вы хотите переподготовку к параллелизму, параллелизму и многопотативной, см. параллелизм, параллелизм и многие нити Санта-Клауса .) Пока я предпочитаю пойти на свои удобные первоклассные примитивы, которые поддерживают параллелизм (см. Chorutines ), получатели этого проекта были более комфортными с Python. Я взял его как возможность тестировать и исследовать!

Одновременно выполнение нескольких задач в Python не невозможна; Это просто занимает небольшую дополнительную работу. Для Hydra основное преимущество находится в преодолении узкого места ввода/вывода (ввода/вывода).

Чтобы проверить веб-страницы, Hydra должен выйти в Интернет и получить их. По сравнению с задачами, которые выполняются только процессором, выходя по сети, сравнительно медленнее. Как медленно?

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

Выполнить типичную инструкцию 1/1 000 000 000 наносец Процессор
забрать из памяти кэша l1 0,5 наносец Процессор
ветви неправильной передаче 5 наносец Процессор
забрать из памяти кэша l2 7 наносец Процессор
Mutex Lock / разблокировка 25 наносец ОЗУ
привлекать от основной памяти 100 наносец ОЗУ
Отправить 2K BYTES в сети 1 Гбит / с 20 000 наносец Сеть
Читайте 1 МБ последовательно из памяти 250 000 наносец ОЗУ
извлекать из нового места диска (искать) 8 000 000 наносец (8 мс) Диск
Читайте 1 МБ последовательно с диска 20 000 000 наносец (20 мс) Диск
Отправить пакет нас в Европу и обратно 150 000 000 наносец (150 мс) Сеть

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

Сравните разницу между Getching из основной памяти и отправка простаю пакета через Интернет. В то время как оба эти операции возникают меньше, чем мгновение ока (буквально) с человеческой точки зрения, вы можете увидеть, что отправка простого пакета через Интернет уже более миллион раз медленнее, чем вытеснение от оперативной памяти. Это разница, что в одной резьбовой программе может быстро накапливаться, чтобы сформировать неприятные узкие места.

В Hydra задача анализа данных ответа и сборки результатов в отчет относительно быстро, поскольку все это происходит на процессоре. Медленная часть выполнения программы, более чем на шесть порядков, является задержкой сети. Не только Hydra нужно получить пакеты, но целые веб-страницы! Одним из способов улучшения производительности Гидры является поиск способа для выполнения задач страницы, не блокируя основной нить.

Python имеет пару вариантов выполнения задач параллельно: несколько процессов или несколько потоков. Эти методы позволяют обойти GIL и ускорить выполнение пару различных способов.

Несколько процессов

Чтобы выполнить параллельные задачи, используя несколько процессов, вы можете использовать Python ProcessPoolExecutor Отказ Бетонный подкласс Исполнитель от chapulent.futures модуль , ProcessPoolExecutor использует пул процессов, порожденных с Многопроцессор модуль чтобы избежать Гила.

Эта опция использует подпроцессы рабочие, которые максимально по умолчанию на количество процессоров на машине. Многопроцессор Модуль позволяет максимально распараллелизовать выполнение функции в процессах, которые действительно могут ускорить вычислительную (или CPU-CORCENT CONECT).

Поскольку основное узкое место для Hydra I/O, а не обработка, которая будет выполняться процессором, мне лучше обслуживаться с помощью нескольких потоков.

Несколько потоков

Умышленные названные, Python’s ThreadPoolexecutor Использует пул резьбы для выполнения асинхронных задач. Также подкласс Исполнитель , Он использует определенное количество максимальных рабочих потоков (по крайней мере, пять по умолчанию, в соответствии с формулой Мин (32, OS.cpu_count () + 4) ) и повторно использует холостые потоки перед началом новых, что делает его довольно эффективным.

Вот фрагмент гидры с комментариями, показывающими, как Hydra использует ThreadPoolexecutor Для достижения параллельных многопотативных блаженств:

# Create the Checker class
class Checker:
    # Queue of links to be checked
    TO_PROCESS = Queue()
    # Maximum workers to run
    THREADS = 100
    # Maximum seconds to wait for HTTP response
    TIMEOUT = 60

    def __init__(self, url):
        ...
        # Create the thread pool
        self.pool = futures.ThreadPoolExecutor(max_workers=self.THREADS)


def run(self):
    # Run until the TO_PROCESS queue is empty
    while True:
        try:
            target_url = self.TO_PROCESS.get(block=True, timeout=2)
            # If we haven't already checked this link
            if target_url["url"] not in self.visited:
                # Mark it as visited
                self.visited.add(target_url["url"])
                # Submit the link to the pool
                job = self.pool.submit(self.load_url, target_url, self.TIMEOUT)
                job.add_done_callback(self.handle_future)
        except Empty:
            return
        except Exception as e:
            print(e)

Вы можете просмотреть полный код в Hydra’s Github Repository Отказ

Одиночная нить в многотехнику

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

time python3 slow-link-check.py https://victoria.dev

real 17m34.084s
user 11m40.761s
sys 0m5.436s


time python3 hydra.py https://victoria.dev

real 0m15.729s
user 0m11.071s
sys 0m2.526s

Однототовая программа, которая блокирует ввод ввода/вывода, побежала около семнадцати минут. Когда я сначала запустил многопотативную версию, она закончила 1 м13,358с – после некоторого профилирования и тюнинга, потребовалось немного до шестнадцати секунд. Опять же, точное время не означает все это; Они будут варьироваться в зависимости от таких факторов, как размер выполнения сайта, скорость вашей сети и баланс вашей программы между накладным расходом управления нитью и преимуществами параллелизма.

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

Оригинал: “https://dev.to/victoria/multithreaded-python-slithering-through-an-i-o-bottleneck-hp0”