请教一个异步 asyncio 的问题

2018-12-02 19:46:44 +08:00
 meowoo

想写一个分析日志的工具,用 asyncio 处理的时候,想大家帮忙看一下哪里有问题,实际处理日志的时候,readlines 的时候,一个 650m 的文件需要 3 秒多,然后往字典里面加的时候需要 8 秒多,所以一个文件要 12 秒多,

现在的效果是,多个文件消耗的时间就是 12*n,就是说并没有提升,不是说 await 的时候,会把当前执行的内容挂起,然后执行下一个任务么,可能是我哪里有问题,麻烦大家给看看,代码如下。

import asyncio

import re

import time

from pathlib import Path

ip_find = re.compile(r'((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))).){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))')

ip_database = dict()

async def ip_address(log):

    """分析 ip"""
    with open(log) as f:
        log_data = f.readlines()
        try:
            for log_ips in log_data:
                ip_search = ip_find.search(log_ips)
                if ip_search:
                    ip_database[ip_search.group(0)] = ip_database.get(ip_search.group(0), 0) + 1
        except Exception as e:
            print(e)

async def generator(log):

    await ip_address(log)

if __name__ == '__main__':

    path = Path(r"D:\anlysis_log")
    start = time()

    loop = asyncio.get_event_loop()
    tasks = [generator(x) for x in path.iterdir()]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
2816 次点击
所在节点    Python
21 条回复
111111111111
2018-12-02 20:02:03 +08:00
asyncio 挂起的是 io 等待,可不包括正则啊
meowoo
2018-12-02 20:04:38 +08:00
@111111111111 log_data = f.readlines() 这个不是 io 等待么,读取文件啊,还是说要函数整体
sunwei0325
2018-12-02 20:13:20 +08:00
用 aiofiles 或者将读取文件的操作放到线程池 /进程池里面
wwwjfy
2018-12-02 20:14:41 +08:00
- asyncio 的任务是显式的,有 await 才会切回到 event loop,不是在 function 前面加 async 就行。可以试试 https://github.com/Tinche/aiofiles
- 这里的瓶颈在磁盘 IO,asyncio 作用应该不会太明显;作用更大的地方是多个 IO 并行执行的时候,这里在读磁盘的时候就去那里执行 CPU 操作,另一个任务可能在等网络请求返回
AlisaDestiny
2018-12-02 20:19:20 +08:00
你没理解 1 楼的意思,他是说你的程序消耗的时间主要在正则查找这里,也就是这条语句:ip_search = ip_find.search(log_ips)。
meowoo
2018-12-02 20:29:24 +08:00
@sunwei0325 好的谢谢,我看下这个库
meowoo
2018-12-02 20:32:28 +08:00
@wwwjfy 我原来理解的是如果 await 的函数在坐 io 等待,就会切换 event loop,就是我在 ip_address 中等待读取文件,这个时间应该切换到下一个 event,等待读取后再继续操作,现在看来理解是有问题的,我去看看 aiofiles 库,多谢。
meowoo
2018-12-02 20:33:01 +08:00
@AlisaDestiny 但是上面文件读取也是在耗时啊,多少会有点儿提升的把
clearT
2018-12-02 23:34:16 +08:00
asyncio 好像不支持异步的读取文件,即使设置为非阻塞读取模式,所以是不会引起切换的。
[Asyncio Wiki]( https://github.com/python/asyncio/wiki/ThirdParty#filesystem)
meowoo
2018-12-03 00:24:13 +08:00
@clearT 看到了 感觉不适合用 python 写了
congeec
2018-12-03 11:34:09 +08:00
@meowoo 问题不在异 asyncio 不支持异步文件读取。run_in_executor 之类的很容易把同步代码封装成异步代码。
瓶颈在#4 @wwwjfy 说的磁盘 IO,你就一个磁盘。正则表达式那块儿可以用 concurrent.futures.ProcessPoolExecutor + asyncio.get_event_loop().run_in_executor() 解决,前提是你有多核处理器

正则那儿可以用 pcre with jit 加速。换个语言也快不到哪儿去
no1xsyzy
2018-12-03 14:08:48 +08:00
正则写错了一处, "." 表示匹配任何字符,而不是 ip 地址中的分隔点。
meowoo
2018-12-03 20:29:56 +08:00
@congeec 现在用 aiofiles 是异步读取的文件,用 async for 去异步执行正则匹配的时候,print 的代码也看到是异步的,但是超级慢,比去掉 async 快了 15 倍 以上 ,现在我晕了已经,不知道咋回事。
meowoo
2018-12-03 20:30:10 +08:00
@no1xsyzy 没错的 前面有 r
congeec
2018-12-04 03:48:09 +08:00
@meowoo 贴代码啊
no1xsyzy
2018-12-04 10:02:45 +08:00
@meowoo 那么你连 r 是什么意思都没明白,`r`是拒绝 Python 字符串转义啊,并不会影响正则的意思。
你要说 `r` 让 "." 不生效(具有正则表达式含义),那么 "\d" "(" ")" "{" "}" "[0-5]" 也不会生效啊。
>>> import re
>>> re.compile(r'((25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))).){3}(25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))').search("3|3|3|
3")
<re.Match object; span=(0, 7), match='3|3|3|3'>
no1xsyzy
2018-12-04 10:08:53 +08:00
另外,不要做干净正则,做个脏正则就行了,
((?:\d{1,3}\.){3}\d{1,3})
在反复出现没搜索到的情况(就是说 [不是] 一行单一个 IP 结束的情况)
meowoo
2018-12-05 01:50:57 +08:00
@no1xsyzy 受教了,是我想偏了,而日志中又没出现这种情况,让我以为没问题
meowoo
2018-12-05 01:55:31 +08:00
@congeec 就是把读取文件改成 aiofiles, for 改成 async for
我看了 debug 之后发现,每次进入 async for 的时候,都会 wait,重新回到 await ip_address(log) 中,然后到 event loop 去看有没有其他等待的 event,然后导致每一行在循环的时候都去 await 一次,结果特别慢,我个人理解,不知道对不对,请指教,async for 不是这样用的么?

代码如下

`async def ip_address(log):`

"""分析 ip"""
async with open(log) as f:
try:
async for log_ips in f:
ip_search = ip_find.search(log_ips)
if ip_search:
ip_database[ip_search.group(0)] = ip_database.get(ip_search.group(0), 0) + 1
except Exception as e:
print(e)
congeec
2018-12-05 03:05:21 +08:00
@meowoo 加 async 并不会让你的同步代码变成异步
好好了解一下异步风格里的”让出执行”权吧
看看下面的文章。划重点: asyncio.gather(), futures, ProcessPoolExecutor。理解 futures 你就知道怎么用同步风格写异步代码
https://blog.konpat.me/python-turn-sync-functions-to-async/
https://docs.python.org/3/library/asyncio-eventloop.html#id14

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/513596

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX