码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2017-12-15 20:09:44      阅读:169      评论:0      收藏:0      [点我收藏+]

标签:loop   tor   syn   服务器端   oop   set   class   0.00   time   

知乎自己在底层造了非常多的轮子,而且也在服务器部署方面和数据获取方面广泛使用 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

 

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

标签:loop   tor   syn   服务器端   oop   set   class   0.00   time   

原文地址:http://www.cnblogs.com/piperck/p/8044632.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!