Gevent 性能和 gevent.loop 的运用和带来的思考

知乎本身在底层造了很是多的轮子,并且也在服务器部署方面和数据获取方面普遍使用 gevent 来提升并发获取数据的能力。如今开始我将结合实际使用与测试慢慢完善本身对 gevent 更全面的使用和扫盲。git

 

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

其实在使用 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 也许不是一个很好的解决方案。app

 

那到底种状况可使咱们的性能得到巨大的提高?来看这个例子:async

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 的状况oop

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

相关文章
相关标签/搜索