【问题标题】:async version runs slower than the non async version异步版本运行速度比非异步版本慢
【发布时间】:2020-02-02 16:44:09
【问题描述】:

我的程序执行以下操作:

  1. 获取 .txt 文件的文件夹
  2. 对于每个文件:

    2.1。读取文件

    2.2 将内容排序为列表并将列表推送到主列表

我在没有任何 async/await 的情况下执行此操作,这些是时间统计信息

real    0m0.036s

user    0m0.018s

sys     0m0.009s

通过下面的异步/等待代码,我得到了

real    0m0.144s

user    0m0.116s

sys     0m0.029s

给出的用例表明我错误地使用了aysncio。

有人知道我做错了什么吗?

import asyncio
import aiofiles
import os

directory = "/tmp"
listOfLists = list()

async def sortingFiles(numbersInList):
    numbersInList.sort()

async def awaitProcessFiles(filename,numbersInList):
    await readFromFile(filename,numbersInList)
    await sortingFiles(numbersInList)
    await appendToList(numbersInList)


async def readFromFile(filename,numbersInList):
    async with aiofiles.open(directory+"/"+filename, 'r') as fin:
        async for line in fin:
            return numbersInList.append(int(line.strip("\n"),10))            
    fin.close()    

async def appendToList(numbersInList):
    listOfLists.append(numbersInList)

async def main():
    tasks=[]
    for filename in os.listdir(directory):
        if filename.endswith(".txt"):  
            numbersInList =list()
            task=asyncio.ensure_future(awaitProcessFiles(filename,numbersInList))
            tasks.append(task)
    await asyncio.gather(*tasks)   

if __name__== "__main__":
    asyncio.run(main())

分析信息:

        151822 function calls (151048 primitive calls) in 0.239 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       11    0.050    0.005    0.050    0.005 {built-in method _imp.create_dynamic}
       57    0.022    0.000    0.022    0.000 {method 'read' of '_io.BufferedReader' objects}
       57    0.018    0.000    0.018    0.000 {built-in method io.open_code}
      267    0.012    0.000    0.012    0.000 {method 'control' of 'select.kqueue' objects}
       57    0.009    0.000    0.009    0.000 {built-in method marshal.loads}
      273    0.009    0.000    0.009    0.000 {method 'recv' of '_socket.socket' objects}
      265    0.005    0.000    0.098    0.000 base_events.py:1780(_run_once)
      313    0.004    0.000    0.004    0.000 {built-in method posix.stat}
      122    0.004    0.000    0.004    0.000 {method 'acquire' of '_thread.lock' objects}
  203/202    0.003    0.000    0.011    0.000 {built-in method builtins.__build_class__}
     1030    0.003    0.000    0.015    0.000 thread.py:158(submit)
     1030    0.003    0.000    0.009    0.000 futures.py:338(_chain_future)
     7473    0.003    0.000    0.003    0.000 {built-in method builtins.hasattr}
     1030    0.002    0.000    0.017    0.000 futures.py:318(_copy_future_state)
       36    0.002    0.000    0.002    0.000 {built-in method posix.getcwd}
     3218    0.002    0.000    0.077    0.000 {method 'run' of 'Context' objects}
     6196    0.002    0.000    0.003    0.000 threading.py:246(__enter__)
     3218    0.002    0.000    0.078    0.000 events.py:79(_run)
     6192    0.002    0.000    0.004    0.000 base_futures.py:13(isfuture)
     1047    0.002    0.000    0.002    0.000 threading.py:222(__init__)

制作一些测试文件...

import random, os
path = <directory name here>
nlines = range(1000)
nfiles = range(1,101)
for n in nfiles:
    fname = f'{n}.txt'
    with open(os.path.join(path,fname),'w') as f:
        for _ in nlines:
            q = f.write(f'{random.randrange(1,10000)}\n')

【问题讨论】:

  • 如果你的代码是cpu-bound(也就是大部分时间都花在排序上),这个结果是合理的,因为异步会增加开销。你有没有分析清楚什么需要时间?
  • 有多少个文件?文件中有多少行(您正在对行进行排序?)?似乎您只需要 async 来打开和读取文件。排序和累加可以在与 open 和 read 相同的协程中完成。我可能会用concurrent.futures 来做这个。
  • 目前测试了 10 个文件,每个文件大约 1000 行。
  • @PiRocks 在描述中添加了分析信息。我认为做异步的重点是做非阻塞 I/O。我还从排序和添加到列表中删除了异步。性能仍然不如非异步。
  • 1000 行文件不是很大;管理异步代码的开销可能大于通过交错处理和 IO 节省的成本。

标签: python asynchronous python-asyncio python-aiofiles


【解决方案1】:

asyncio 对本地文件毫无意义。这就是原因,即使是python标准库也没有。

async for line in fin:

考虑上面的行。事件循环为读取的每一行暂停协同程序并执行其他一些协同程序。这意味着 cpu 缓存中文件的以下行将被丢弃,以便为下一个协程腾出空间。 (但它们仍会在 RAM 中)。

什么时候应该使用 aiofile?

假设您已经在程序中使用了异步代码,并且有时您必须进行一些文件处理。如果文件处理是在同一个事件循环中完成的,那么所有其他协程都将被阻塞。在这种情况下,您可以使用 aiofiles 或在不同的执行程序中进行处理。

如果程序所做的只是从文件中读取。顺序执行它们会更快,以便充分利用缓存。从一个文件跳转到另一个文件就像一个线程上下文切换,应该让它变慢。

【讨论】:

  • 只是为了补充您的答案,我认为在这里使用 asyncio 是不合适的。 Asyncio 用于 IO 绑定进程。在这里,我们正在执行一些列表操作,例如追加和排序。除非我弄错了,否则这些不是 IO-bound 进程。考虑到这一点,对这些操作使用协程并没有真正的意义。
  • 对于非常大的文件,如果您不对读取的数据进行太多处理,读取它们可能会成为瓶颈,但我同意考虑到 Python 的性能,这可能很难实现。
猜你喜欢
  • 2021-03-19
  • 1970-01-01
  • 2016-08-20
  • 2015-04-17
  • 1970-01-01
  • 2014-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多