对于这个 I/O 密集型操作,为什么 asyncio 库比线程慢?

2024-01-14

我正在编写一个 python 程序,用于枚举网站的域名。例如,'a.google.com'。

首先,我使用了threading模块来执行此操作:

import string
import time
import socket
import threading
from threading import Thread
from queue import Queue

'''
enumerate a site's domain name like this:
1-9 a-z + .google.com
1.google.com
2.google.com
.
.
1a.google.com
.
.
zz.google.com

'''

start = time.time()
def create_host(char):
    '''
    if char is '1-9a-z'
    create char like'1,2,3,...,zz'
    '''
    for i in char:
        yield i
    for i in create_host(char):
        if len(i)>1:
            return False
        for c in char:
            yield c + i


char = string.digits + string.ascii_lowercase
site = '.google.com'


def getaddr():
    while True:
        url = q.get()
        try:
            res = socket.getaddrinfo(url,80)
            print(url + ":" + res[0][4][0])
        except:
            pass
        q.task_done()

NUM=1000  #thread's num
q=Queue()

for i in range(NUM):
    t = Thread(target=getaddr)
    t.setDaemon(True)
    t.start()

for host in create_host(char):
    q.put(host+site)
q.join()

end = time.time()

print(end-start)

'''
used time:
9.448670148849487
'''

后来我读了一本书,说在某些情况下协程比线程更快。所以,我重写了代码来使用asyncio:

import asyncio
import string
import time


start = time.time()
def create_host(char):
    for i in char:
        yield i
    for i in create_host(char):
        if len(i)>1:
            return False
        for c in char:
            yield c + i


char = string.digits + string.ascii_lowercase
site = '.google.com'

@asyncio.coroutine
def getaddr(loop, url):
    try:
        res = yield from loop.getaddrinfo(url,80)
        print(url + ':' + res[0][4][0])
    except:
        pass

loop = asyncio.get_event_loop()
coroutines = asyncio.wait([getaddr(loop, i+site) for i in create_host(char)])
loop.run_until_complete(coroutines)

end = time.time()

print(end-start)


'''
time 
120.42313003540039
'''

为什么是asyncio的版本getaddrinfo这么慢吗?我是否以某种方式滥用了协程?


首先,我无法重现与您在我的 Linux 机器上看到的性能差异几乎一样大的性能差异。我一直看到线程版本大约需要 20-25 秒,而线程版本大约需要 24-34 秒。asyncio版本。

现在,为什么是asyncio慢点?有几件事促成了这一点。首先,asyncio版本必须按顺序打印,但线程版本则不需要。打印是 I/O,因此可以在打印时释放 GIL。这意味着两个或更多线程可能会同时打印,尽管实际上这种情况可能不会经常发生,并且可能不会对性能产生太大影响。

其次,也是更重要的一点是,the asyncio的版本getaddrinfo实际上是只是打电话socket.getaddrinfo in a ThreadPoolExecutor https://hg.python.org/cpython/file/6d91c4f40ba1/Lib/asyncio/base_events.py#l461:

def getaddrinfo(self, host, port, *,
                family=0, type=0, proto=0, flags=0):
    if self._debug:
        return self.run_in_executor(None, self._getaddrinfo_debug,
                                    host, port, family, type, proto, flags)
    else:
        return self.run_in_executor(None, socket.getaddrinfo,
                                    host, port, family, type, proto, flags)

这是使用默认的ThreadPoolExecutor为了这,只有五个线程 https://hg.python.org/cpython/file/6d91c4f40ba1/Lib/asyncio/base_events.py#l40*:

# Argument for default thread pool executor creation.
_MAX_WORKERS = 5

对于这个用例来说,这远没有您想要的那么多并行性。为了让它表现得更像threading版本,你需要使用ThreadPoolExecutor有 1000 个线程,通过将其设置为默认执行器loop.set_default_executor:

loop = asyncio.get_event_loop()
loop.set_default_executor(ThreadPoolExecutor(1000))
coroutines = asyncio.wait([getaddr(loop, i+site) for i in create_host(char)])
loop.run_until_complete(coroutines)

现在,这将使行为更加等同于threading,但现实是你真的没有使用异步 I/O - 你只是使用threading使用不同的 API。所以你在这里能做的最好的就是与threading例子。

最后,您并没有在每个示例中真正运行等效的代码 -threading版本正在使用一个工作池,它们共享一个queue.Queue,而asyncioversion 正在为 url 列表中的每个项目生成一个协程。如果我做asyncio版本使用asyncio.Queue和协程池,除了删除打印语句和创建更大的默认执行器之外,我在两个版本中获得了基本相同的性能。这是新的asyncio code:

import asyncio
import string
import time
from concurrent.futures import ThreadPoolExecutor

start = time.time()
def create_host(char):
    for i in char:
        yield i
    for i in create_host(char):
        if len(i)>1:
            return False
        for c in char:
            yield c + i


char = string.digits + string.ascii_lowercase
site = '.google.com'

@asyncio.coroutine
def getaddr(loop, q):
    while True:
        url = yield from q.get()
        if not url:
            break
        try:
            res = yield from loop.getaddrinfo(url,80)
        except:
            pass

@asyncio.coroutine
def load_q(loop, q):
    for host in create_host(char):
        yield from q.put(host+site)
    for _ in range(NUM):
        yield from q.put(None)

NUM = 1000
q = asyncio.Queue()

loop = asyncio.get_event_loop()
loop.set_default_executor(ThreadPoolExecutor(NUM))
coros = [asyncio.async(getaddr(loop, q)) for i in range(NUM)]
loop.run_until_complete(load_q(loop, q))
loop.run_until_complete(asyncio.wait(coros))

end = time.time()

print(end-start)

以及每个的输出:

dan@dandesk:~$ python3 threaded_example.py
20.409344911575317
dan@dandesk:~$ python3 asyncio_example.py
20.39924192428589

但请注意,由于网络原因,存在一些变化。两者有时都会比这慢几秒钟。

* 注意,在Python 3.8及以上版本中,默认ThreadPoolExecutor创造min(32, os.get_cpu_count() + 4)线程。根据您的机器有多少个核心,这可能会导致创建足够的线程以提高性能asyncio为了更紧密地匹配threading例子。你必须测试一下才能看到。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

对于这个 I/O 密集型操作,为什么 asyncio 库比线程慢? 的相关文章

  • Pycharm Python 控制台不打印输出

    我有一个从 Pycharm python 控制台调用的函数 但没有显示输出 In 2 def problem1 6 for i in range 1 101 2 print i end In 3 problem1 6 In 4 另一方面 像
  • 如何打印没有类型的defaultdict变量?

    在下面的代码中 from collections import defaultdict confusion proba dict defaultdict float for i in xrange 10 confusion proba di
  • 如何在 Sublime Text 2 的 OSX 终端中显示构建结果

    我刚刚从 TextMate 切换到 Sublime Text 2 我非常喜欢它 让我困扰的一件事是默认的构建结果显示在 ST2 的底部 我的程序产生一些很长的结果 显示它的理想方式 如在 TM2 中 是并排查看它们 如何在 Mac 操作系统
  • 更改自动插入 tkinter 小部件的文本颜色

    我有一个文本框小部件 其中插入了三条消息 一条是开始消息 一条是结束消息 一条是在 单位 被摧毁时发出警报的消息 我希望开始和结束消息是黑色的 但被毁坏的消息 参见我在代码中评论的位置 插入小部件时颜色为红色 我不太确定如何去做这件事 我看
  • 如何等到 Excel 计算公式后再继续 win32com

    我有一个 win32com Python 脚本 它将多个 Excel 文件合并到电子表格中并将其另存为 PDF 现在的工作原理是输出几乎都是 NAME 因为文件是在计算 Excel 文件内容之前输出的 这可能需要一分钟 如何强制工作簿计算值
  • SQL Alchemy 中的 NULL 安全不等式比较?

    目前 我知道如何表达 NULL 安全的唯一方法 SQL Alchemy 中的比较 其中与 NULL 条目的比较计算结果为 True 而不是 NULL 是 or field None field value 有没有办法在 SQL Alchem
  • keras加载模型错误尝试将包含17层的权重文件加载到0层的模型中

    我目前正在使用 keras 开发 vgg16 模型 我用我的一些图层微调 vgg 模型 拟合我的模型 训练 后 我保存我的模型model save name h5 可以毫无问题地保存 但是 当我尝试使用以下命令重新加载模型时load mod
  • 从列表中的数据框列中搜索部分字符串匹配 - Pandas - Python

    我有一个清单 things A1 B2 C3 我有一个 pandas 数据框 其中有一列包含用分号分隔的值 某些行将包含与上面列表中的一项的匹配 它不会是完美的匹配 因为它在其中包含字符串的其他部分 该列 例如 该列中的一行可能有 哇 这里
  • NameError:名称“urllib”未定义”

    CODE import networkx as net from urllib request import urlopen def read lj friends g name fetch the friend list from Liv
  • python pandas 中的双端队列

    我正在使用Python的deque 实现一个简单的循环缓冲区 from collections import deque import numpy as np test sequence np array range 100 2 resha
  • Abaqus 将曲面转化为集合

    我一直试图在模型中找到两个表面的中心 参见照片 但未能成功 它们是元素表面 面 查询中没有选项可以查找元素表面的中心 只能查找元素集的中心 找到节点集的中心也很好 但是我的节点集没有出现在工具 gt 查询 gt 质量属性选项中 而且我找不到
  • Geopandas 设置几何图形:MultiPolygon“等于 len 键和值”的 ValueError

    我有 2 个带有几何列的地理数据框 我将一些几何图形从 1 个复制到另一个 这对于多边形效果很好 但对于任何 有效 多多边形都会返回 ValueError 请指教如何解决这个问题 我不知道是否 如何 为什么应该更改 MultiPolygon
  • 如何将 PIL 图像转换为 NumPy 数组?

    如何转换 PILImage来回转换为 NumPy 数组 这样我就可以比 PIL 进行更快的像素级转换PixelAccess允许 我可以通过以下方式将其转换为 NumPy 数组 pic Image open foo jpg pix numpy
  • 为美国东部以外地区的 Cloudwatch 警报发送短信?

    AWS 似乎没有为美国东部以外的 SNS 主题订阅者提供 SMS 作为协议 我想连接我的 CloudWatch 警报并在发生故障时接收短信 但无法将其发送到 SMS YES 经过一番挖掘后 我能够让它发挥作用 它比仅仅选择一个主题或输入闹钟
  • 如何在 Django 中使用并发进程记录到单个文件而不使用独占锁

    给定一个在多个服务器上同时执行的 Django 应用程序 该应用程序如何记录到单个共享日志文件 在网络共享中 而不保持该文件以独占模式永久打开 当您想要利用日志流时 这种情况适用于 Windows Azure 网站上托管的 Django 应
  • 类型错误:只能使用标量值执行操作

    如果您能让我知道如何为所提供的表格绘制一些信息丰富的图表 我将不胜感激here https www iasplus com en resources ifrs topics use of ifrs 例如 我需要一个名为 国内非上市公司 非上
  • Python:计算字典的重复值

    我有一本字典如下 dictA unit1 test1 alpha unit1 test2 beta unit2 test1 alpha unit2 test2 gamma unit3 test1 delta unit3 test2 gamm
  • VSCode:调试配置中的 Python 路径无效

    对 Python 和 VSCode 以及 stackoverflow 非常陌生 直到最近 我已经使用了大约 3 个月 一切都很好 当尝试在调试器中运行任何基本的 Python 程序时 弹出窗口The Python path in your
  • 如何使用google colab在jupyter笔记本中显示GIF?

    我正在使用 google colab 想嵌入一个 gif 有谁知道如何做到这一点 我正在使用下面的代码 它并没有在笔记本中为 gif 制作动画 我希望笔记本是交互式的 这样人们就可以看到代码的动画效果 而无需运行它 我发现很多方法在 Goo
  • 改变字典的哈希函数

    按照此question https stackoverflow com questions 37100390 towards understanding dictionaries 我们知道两个不同的字典 dict 1 and dict 2例

随机推荐