асинхронный медленнее, чем синхронный - PullRequest
0 голосов
/ 24 апреля 2018

Моя программа выполняет следующие действия:

  • принимает папку с текстовыми файлами
  • для каждого файла:
    • читает файл
    • делает POSTзапрос к API в localhost с использованием содержимого файла
    • синтаксический анализ XML-ответа (не в примере ниже)

Я был обеспокоен производительностью синхронной версииПрограмма пыталась использовать aiohttp, чтобы сделать его асинхронным (это моя первая попытка асинхронного программирования на Python, кроме Scrapy).Оказалось, что асинхронный код занял в 2 раза больше времени, и я не понимаю, почему.

СИНХРОННЫЙ КОД (152 секунды)

url = "http://localhost:6090/api/analyzexml"
package = #name of the package I send in each requests
with open("template.txt", "r", encoding="utf-8") as f:
    template = f.read()

articles_path = #location of my text files

def fetch(session, url, article_text):
    data = {"package": package, "data": template.format(article_text)}
    response = session.post(url, data=json.dumps(data))
    print(response.text)

files = glob(os.path.join(articles_path, "*.txt"))

with requests.Session() as s:
    for file in files:
        with open(file, "r", encoding="utf-8") as f:
                article_text = f.read()
        fetch(s, url, article_text)

Результаты профилирования:

+--------+---------+----------+---------+----------+-------------------------------------------------------+
| ncalls | tottime | percall  | cumtime | percall  |               filename:lineno(function)               |
+--------+---------+----------+---------+----------+-------------------------------------------------------+
|    849 |   145.6 |   0.1715 |   145.6 |   0.1715 | ~:0(<method 'recv_into' of '_socket.socket' objects>) |
|      2 |   1.001 |   0.5007 |   1.001 |   0.5007 | ~:0(<method 'connect' of '_socket.socket' objects>)   |
|    365 |   0.772 | 0.002115 |   1.001 | 0.002742 | ~:0(<built-in method builtins.print>)                 |
+--------+---------+----------+---------+----------+-------------------------------------------------------+

(WANNABE) АСИНХРОННЫЙ КОД (327 секунд)

async def fetch(session, url, article_text):
    data = {"package": package, "data": template.format(article_text)}
    async with session.post(url, data=json.dumps(data)) as response:
        return await response.text()

async def process_files(articles_path):
    tasks = []

    async with ClientSession() as session:
        files = glob(os.path.join(articles_path, "*.txt"))
        for file in files:
            with open(file, "r", encoding="utf-8") as f:
                article_text = f.read()
            task = asyncio.ensure_future(fetch(session=session, 
                                        url=url, 
                                        article_text=article_text
                                        ))
            tasks.append(task)
            responses = await asyncio.gather(*tasks)
            print(responses)


loop = asyncio.get_event_loop()
future = asyncio.ensure_future(process_files(articles_path))
loop.run_until_complete(future)

Результаты профилирования:

 +--------+---------+---------+---------+---------+-----------------------------------------------+
    | ncalls | tottime | percall | cumtime | percall |           filename:lineno(function)           |
    +--------+---------+---------+---------+---------+-----------------------------------------------+
    |   2278 |     156 | 0.06849 |     156 | 0.06849 | ~:0(<built-in method select.select>)          |
    |    365 |   128.3 |  0.3516 |   168.9 |  0.4626 | ~:0(<built-in method builtins.print>)         |
    |    730 |   40.54 | 0.05553 |   40.54 | 0.05553 | ~:0(<built-in method _codecs.charmap_encode>) |
    +--------+---------+---------+---------+---------+-----------------------------------------------+

Я явно что-то упускаю в этой концепции.Может ли кто-нибудь также помочь мне понять, почему печать в асинхронной версии занимает так много времени (см. Профилирование).

1 Ответ

0 голосов
/ 24 апреля 2018

Потому что это не асинхронно:)

Посмотрите на свой код: вы делаете responses = await asyncio.gather(*tasks) для каждого файла , поэтому вы в основном запускаете выборку синхронно, каждый раз платя всю цену за обработку сопрограмм.

Полагаю, это просто ошибка отступа; если вы удалите отступ responses = await asyncio.gather(*tasks), чтобы он прошел цикл for file in files, вы действительно начнете tasks параллельно.

...