Простой параллелизм, реализованный на языке Python


Цель вопроса: узнать больше о способах реализации многопоточности в Python и экспериментировать.

Контекст: я хочу, чтобы сосчитать всех слов во всех файлах, которые соответствуют определенному шаблону. Идея заключается в том, что я могу вызвать функцию count_words('/foo/bar/*.txt') и все слова (т. е., строки, разделенные одним или более пробельных символов) будут учитываться.

В реализации я ищу пути реализации count_words используя параллелизм. До сих пор мне удалось использовать multiprocessing и asyncio.

Что вы думаете о этих двух подходов? Вы увидеть альтернативные подходы, чтобы сделать ту же задачу? Насчет стиля кода, я должен реализовать специальный класс для каждого подхода?

Я не использовал threading как я заметил улучшение производительности не впечатляет из-за ограничений на Python Гиль.

import asyncio
import multiprocessing
import time
from pathlib import Path
from pprint import pprint


def count_words(file):
    with open(file) as f:
        return sum(len(line.split()) for line in f)


async def count_words_for_file(file):
    with open(file) as f:
        return sum(len(line.split()) for line in f)


def async_count_words(path, glob_pattern):
    event_loop = asyncio.get_event_loop()
    try:
        print("Entering event loop")
        for file in list(path.glob(glob_pattern)):
            result = event_loop.run_until_complete(count_words_for_file(file))
            print(result)
    finally:
        event_loop.close()


def multiprocess_count_words(path, glob_pattern):
    with multiprocessing.Pool(processes=8) as pool:
        results = pool.map(count_words, list(path.glob(glob_pattern)))
        pprint(results)


def sequential_count_words(path, glob_pattern):
    for file in list(path.glob(glob_pattern)):
        print(count_words(file))


if __name__ == '__main__':
    benchmark = []
    path = Path("../data/gutenberg/")
    # no need for benchmark on sequential_count_words, it is very slow!
    # sequential_count_words(path, "*.txt")

    start = time.time()
    async_count_words(path, "*.txt")
    benchmark.append(("async version", time.time() - start))

    start = time.time()
    multiprocess_count_words(path, "*.txt")
    benchmark.append(("multiprocess version", time.time() - start))

    print(*benchmark)

Для моделирования большого количества файлов, я скачал книги из Проекта Гутенберг (https://gutenberg.org/) и использовать следующую команду, чтобы создать несколько дубликатов одного и того же файла.

for i in {000..99}; do cp 56943-0.txt $(openssl rand -base64 12)-$i.txt; done


Комментарии
1 ответ

async def count_words_for_file(file):
with open(file) as f:
return sum(len(line.split()) for line in f)

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

В вашем случае почти все грани процессора (расщепление линий) и дискового ввода/вывода (чтение файлов). Для может быть распараллелен через процессы только (из-за Джил), для второго можно использовать темы (раз Жиль не влияет на дисковые операции ввода-вывода). В обоих случаях можно использовать правило процесс ввода-вывода с run_in_executor.

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

3
ответ дан 21 мая 2018 в 09:05 Источник Поделиться