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

相關文章
相關標籤/搜索