Python异步文件处理有何更优方案?asyncio.to_thread性能不如阻塞如何解决
你当前实现的性能瓶颈来自大量细碎的asyncio.to_thread调用产生的额外开销:每个文件操作(创建目录、打开文件、写入内容)都单独触发一次线程调度、GIL竞争,1000个任务累计产生数千次切换开销,完全抵消了异步非阻塞的收益,所以性能弱于纯同步实现。
以下是可落地的优化方案:
合并单任务IO操作到单次
to_thread调用
不要把单个任务的多个文件操作拆成多次独立的线程调度,把所有相关操作打包成一个同步函数后一次性丢到to_thread执行,最多可以减少70%以上的线程切换开销。
优化后代码示例:# 把单任务的所有文件IO操作打包为同步函数 def sync_file_op(i): path = Path(f'io_test/async/{i}') path.mkdir(parents=True, exist_ok=True) with open(f'{path}/f.py', 'w') as f: f.write("print('Hello World!')") async def aio(i): # 单次线程调度完成所有操作,无额外切换开销 await asyncio.to_thread(sync_file_op, i)这个优化完成后,异步版本的性能可以接近同步实现的90%以上,同时完全不会阻塞事件循环。
限制并发度避免资源竞争
文件系统的IOPS有物理上限,一次性提交上千个IO任务会触发大量文件锁竞争、内核IO调度开销,反而降低处理速度。可以用asyncio.Semaphore限制同时运行的IO任务数:机械盘建议设置为10-20,SSD建议设置为30-50,也可以根据实际磁盘性能调优。
优化后代码示例:async def aio(i, semaphore): async with semaphore: await asyncio.to_thread(sync_file_op, i) async def async_main(n): # 限制最多同时执行32个文件IO任务 semaphore = asyncio.Semaphore(32) ts = time.time() coroutines = [aio(i, semaphore) for i in range(n)] await asyncio.gather(*coroutines) te = time.time() print(f'ASYNC IO | Took {te - ts} seconds.')批量聚合IO操作
如果业务逻辑允许,把多个小文件的创建、写入操作合并为批量操作,比如预先创建所有父目录、批量写入内容后刷盘,减少系统调用次数,性能可以进一步提升。
关于aiofile无性能优势的说明
aiofile底层依赖操作系统原生异步文件IO接口(Linux libaio/Windows IOCP),但这类接口仅对Direct IO模式的大文件读写有明显收益;普通缓存IO场景下,操作系统本身也是用内核线程模拟异步操作,和to_thread的实现原理几乎一致,再叠加aiofile的用户态封装开销,小文件读写场景下没有性能优势是正常现象。
内容的提问来源于stack exchange,提问作者Adrian Lazăr

