【问题标题】:Python asyncio runs slowerPython asyncio 运行速度较慢
【发布时间】:2020-06-14 15:01:12
【问题描述】:

我是 Python 和并行执行和异步的新手。我做错了吗?我的代码运行速度较慢(或最多等于)以传统方式运行脚本所需的时间,没有异步。

import asyncio, os, time, pandas as pd
start_time = time.time()

async def main():
    coroutines = list()
    for root, dirs, files in os.walk('.', topdown=True):
        for file in files:
            coroutines.append(cleaner(file))
        await asyncio.gather(*coroutines)

async def cleaner(file):
 df = pd.read_csv(file, sep='\n', header=None, engine='python', quoting=3)
 df = df[0].str.strip(' \t"').str.split('[,|;: \t]+', 1, expand=True).rename(columns={0: 'email', 1: 'data'}) 
 df[['email', 'data']].to_csv('x1', sep=':', index=False, header=False, mode='a', compression='gzip')


asyncio.run(main())
print("--- %s seconds ---" % (time.time() - start_time))

【问题讨论】:

  • cleaner 是否同步读/写文件系统?如果一个线程中的处理重叠等待另一个线程中的 I/O,则运行并行清理器而不是 asyncio 可能确实更快。如果您正在执行释放 GIL 并允许真正并行性的 pandas 操作,它会更快。
  • asyncio 可能不会比多线程应用程序快,只要它们中的任何一个有足够的工作来消耗整个 cpu。 asyncio 的优点是它不必锁定共享数据以及随之而来的所有问题。
  • 确实...清洁器确实写入了多个文件。因此,输出目的地在各种协程之间共享。你对德莱尼有什么建议?
  • 假设每个文件的处理独立于其他文件,这是multiprocessing.Pool 的理想选择,甚至可以将cleaner 拉入单独的python 文件并使用subprocess 运行它。我将发布一个示例。
  • 您的代码无法更新,因为概念存在缺陷。仅当您有多个“等待”某个事件或资源的任务并且一个任务等待另一个任务时,Asyncio 才具有性能优势。您的代码必须具有利用这一点的可等待对象。声明一个函数“await def”不会带来任何好处,除非它包含一个“await”语句,并且await语句确实有一些它必须等待的东西。

标签: python python-asyncio


【解决方案1】:

您的工作负载似乎是读取文件 --> 使用 pandas 处理 --> 写入文件。这是多处理的理想选择,因为每个工作项都非常独立。 pandas 读取/写入文件系统的例程,就像任何阻塞操作一样,不适合使用 asyncio,除非您在 asyncio 的线程或进程池中运行它们。

相反,这些多个操作是真正的并行执行的良好候选者,而 asyncio 没有提供给您。 (它的线程和进程池也是不错的选择)。

import multiprocessing as mp
import os

def walk_all_files(path):
    for root, dirs, files in os.walk('.', topdown=True):
        for file in files:
            yield os.path.join(root, file)

def cleaner(path):
    return "sparkly"

def clean_all(path="."):
    files = list(walk_all_files(path))
    # using cpu*2 assuming that there is a lot of cpu heavy
    # work that can be done by some processes while others
    # wait on I/O. This is only a guess.
    cpu_count = min(len(files), mp.cpu_count()*2)
    with mp.Pool(cpu_count) as pool:
        # assuming processing is fairly long but also kindof random depending on
        # file contents, setting chunksize to 1 so that subprocess gets new work
        # item from parent on each round. You could set it higher to have fewer
        # interactions between parent and worker.
        result = pool.map(cleaner, files, chunksize=1)

if __name__ == "__main__":
    clean_all(".")

【讨论】:

  • 感谢 tdelaney:抱歉,我对此太陌生。我无法遵循您的代码。我已经更新了我的问题,以演示清洁功能
  • 我得到一个错误:AttributeError: module 'multiprocessing' has no attribute 'map'
  • 对此感到抱歉。应该是pool.map
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-25
  • 1970-01-01
  • 1970-01-01
  • 2010-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多