zoukankan      html  css  js  c++  java
  • Gevent 性能和 gevent.loop 的运用和带来的思考

    知乎自己在底层造了非常多的轮子,而且也在服务器部署方面和数据获取方面广泛使用 gevent 来提高并发获取数据的能力。现在开始我将结合实际使用与测试慢慢完善自己对 gevent 更全面的使用和扫盲。

    在对 gevent loop 的使用上,gevent tutorial 介绍得非常敷衍,以至于完全不知道他的使用办法。这里我将结合 timeit 测试更详细的介绍一下 gevnet.loop 的使用。以及他的父类 Group 的使用。

    其实在使用 gevent 上面我个人一直有一个误区,就是我使用并发的 gevent 一定比我平时线性的操作速度更快,其实不是这样。让我们来看一个例子:

    
    
    import timeit
    import gevent

    def
    task1(): pass def task(): for i in range(50): pass def async(): x = gevent.spawn(task) x.join() def sync(): for i in range(50): task1() print timeit.timeit(stmt=async, setup=''' from __main__ import task, async, sync ''', number=1000) print '同步开始了' print timeit.timeit(stmt=sync, setup=''' from __main__ import task, async, sync ''', number=1000)

    output:

    0.0216090679169
    同步开始了
    0.00430107116699

    可以看到,我们同样跑一样的函数调用,如果使用 gevent.spawn 一个调用,我们会话费更多的资源,这导致了我们甚至没有线性完成得快。你可能会说,这是当然了,因为这里只 spwan 了一个 gevent 的 greenlet 实例。如果我们调用多个呢?

    import timeit
    import gevent
    
    
    def async1():
        p = []
        for i in range(50):
            p.append(gevent.spawn(task1))
        gevent.joinall(p)
    
    
    def task1():
        pass
    
    
    def sync():
        for i in range(50):
            task1()
    
    
    print timeit.timeit(stmt=async1, setup='''
    from __main__ import task, async1, sync
    ''', number=1000)
    print '同步开始了'
    print timeit.timeit(stmt=sync, setup='''
    from __main__ import task, async1, sync
    ''', number=1000)
    
    output:
    1.21793103218
    同步开始了
    0.0048680305481

    情况似乎变得更糟糕了。。。。我们同时 spawn 了 50个 greenlet 实例实图一次性搞定这个事情,但是速度甚至变得更慢了。由此我们可以得出一个结论,也许在并不是在网络请求或者需要等待切换的情况下,使用 gevent 也许不是一个很好的解决方案。

    那到底种情况可以使我们的性能获得巨大的提升?来看这个例子:

    import timeit
    import gevent
    
    
    def async1():
        p = []
        for i in range(50):
            p.append(gevent.spawn(task1))
        gevent.joinall(p)
    
    
    def task1():
        gevent.sleep(0.001)
    
    
    def sync():
        for i in range(50):
            task1()
    
    
    print timeit.timeit(stmt=async1, setup='''
    from __main__ import task1, async1, sync
    ''', number=100)
    print '同步开始了'
    print timeit.timeit(stmt=sync, setup='''
    from __main__ import task1, async1, sync
    ''', number=100)
    
    
    output:
    0.25629901886
    同步开始了
    6.91364789009

    可以看出来,这次我 spawn 50个一起跑,就远远快于线性了。因为在线性的情况下,我们每次都会在 task1 任务运行的时候阻塞 0.001s, 但是 gevent 使得 async 函数几乎不受等待影响。非常快速的解决了这个问题。其实这个环境在我们进行网络 io 的时候非常常见。比如我们向某个地址下载图片,如果我们线性下载图片,我们需要等待第一张图片下载完成之后才能进行第二张图片的下载,但是我们使用 gevent 并发下载图片,我们可以先开始下载图片,然后在等待的时候切换到别的任务继续进行下载。当下载完毕之后我们会切换回来完成下载。不用等待任何一个任务下载完成,大大的提高了效率。

    gevent 的 pool 函数可以控制并发的时候最多使用 greenlet 的数量。 这里我循环了50次,但是当我们在进行 io 的时候,我们设置了 1w 次,那么也会起 10000 个协程来运行这个程序,对于性能我们是不知道的。有可能会直接堵死服务器端,所以我们需要对此进行控制,我们限制最多同时使用 20 个 greenlet 实例进行处理,当有任务完成之后我们再开始别的任务,更好的控制我们的请求以及维护相当的效率让我们来看几个数据:

    开 10个 greenlet 的情况

    import timeit
    import gevent
    from gevent.pool import Pool
    
    x = Pool(40)
    
    def async1():
        for i in range(50):
            x.spawn(task1)
        x.join()
    
    
    def task1():
        gevent.sleep(0.001)
    
    
    def sync():
        for i in range(50):
            task1()
    
    
    print timeit.timeit(stmt=async1, setup='''
    from __main__ import task1, async1, sync
    ''', number=100)
    print '同步开始了'
    print timeit.timeit(stmt=sync, setup='''
    from __main__ import task1, async1, sync
    ''', number=100)
    
    
    output:

    0.813331842422
    同步开始了
    6.89506411552

     

    开 40 个实例的情况:

    0.366757154465
    同步开始了
    6.78097295761

    开80 个实例的情况:

    0.222685098648
    同步开始了
    6.77246403694

    开10000个的情况:

    0.227874994278
    同步开始了
    6.81039714813

    可以看到当我们超过阀值之后,开更多的实例已经没有任何意义了。而且有可能还造成一些性能上的浪费,所以选择一个合适的实例数量即可。

    另外还有一个速度更快的函数可以提供使用:

    def async1():
        for i in range(50):
            x.imap(task1)

    官方文档上还有一句话,就是如果对出的结果并不要求顺序的话可以使用imap_unordered,速度更快:

    def async1():
        for i in range(50):
            x.imap_unordered(task1)

    pool饱和的情况下 上面的例子差不多只要 0.8s 就能处理完,imap 需要1s。使用join需要 0.22s。

    Reference:

    http://hhkbp2.github.io/gevent-tutorial/#_8  gevent-tutorial

  • 相关阅读:
    EasyUI-datagrid中load,reload,loadData方法的区别
    CAD ObjectARX扩展工具的源码(二)
    CAD ObjectARX扩展工具的源码(一)
    ObjectARX常用类和函数
    acdb类杂记
    ARX错误1 LINK : fatal error LNK1104: 无法打开文件“D:win10Desktop123x64Debugxxx.arx”
    关键功能函数是 (VLAX-Add-Cmd)。
    ObjectARX_2020函数收集
    得到ARX程序当前的路径
    objectarx向量的用法2
  • 原文地址:https://www.cnblogs.com/piperck/p/8044632.html
Copyright © 2011-2022 走看看