同期、非同期(gevent,asyncio)、マルチスレッド(threading)効率比較

4185 ワード

3つのケースで50ページを収集するのに要する時間を比較すると,マルチスレッドはgeventよりも効率的にはるかに高いことが分かった.最初のテストではmonkeyというパッチは使用されず、socketは呼び出しをブロックし、効率は向上しなかった.同期して実行されたため、monkeyパッチを使用すると、socketをコラボレーション実行に変え、効率が大幅に向上した.
Pythonの実行環境では、モジュール、クラス、関数など、実行時にほとんどのオブジェクトを変更できます.これは一般的に驚くべき悪いアイデアです.それは「暗黙的な副作用」を生み出したため、問題が発生するとデバッグが非常に難しいことが多いからです.とはいえ、極端な場合、ライブラリがPython自体の基礎的な動作を修正する必要がある場合、サルパッチが役に立ちます.この場合、geventは、socket、ssl、threading、selectなどのモジュールを含む標準ライブラリの大部分のブロックシステム呼び出しを修正し、コラボレーション実行に変更することができます.
例えば、Redisのpythonバインディングは、通常のtcp socketを使用してredis−serverインスタンスと通信する.gevent.を簡単に呼び出すことでmonkey.patch_all()は、redisのバインドコラボレーション式のスケジューリング要求をgeventスタックの他の部分と一緒に動作させることができる.
これにより、コードを1行でも書かずにgeventと共同で作業できないライブラリを組み合わせることができます.サルパッチは依然として邪悪(evil)であるが、この場合は「役に立つ邪悪(useful evil)」である.
実行結果はそれぞれ
同期実行:4.50 s gevent非同期:0.47 s threadingマルチスレッド:0.58 s更新asyncio:1.1 s geventは同時に実行するのではなく、順次実行するのか、出力結果を乱さず、マルチスレッドは順次実行せず、出力結果を乱す.ネットワークの状況が良い(IO操作の遅延が低い)場合、geventは少し効率を高めることができ、IO操作が時間がかかる場合はgevent効率が大幅に向上し、効率が最も高いのはマルチスレッドです.gevent: greenlet IO , , greenlet, IO , 。 IO , , gevent , greenlet , IO。 gevent-廖雪峰の公式サイト
# /usr/bin/python3
# -*- coding: utf-8 -*-
# Copyright (c) 2017 - walker 

import gevent
import requests
import re
import timeit
import codecs
from threading import Thread
from gevent import monkey
monkey.patch_socket()


def get_title(url,title_list=[]):
    try:
        r = requests.get(url,timeout=5)
        r.encoding = 'utf8'
        html = r.text
        title = re.search(r'(.*?)',html).group(1)
    except TimeoutError:
        title = ''
    if title:
        title_list.append(title)
    return title

def get_url():
    baseurl = 'http://www.baidu.com/s?cl=3&tn=baidutop10&fr=top1000&wd=%s'
    f = codecs.open('muci.txt','r','utf8')
    url_list = []
    for key in f.readlines():
        url_list.append(baseurl % key.strip())
    return url_list


url_list = get_url()

def run1():
    title_list = []
    for url in url_list:
        get_title(url,title_list)
        # title_list.append(title)
    print('Sync result length:',len(title_list),title_list)
    return title_list

def run2():
    title_list = []
    threads = [gevent.spawn(get_title,url,title_list) for url in url_list]
    gevent.joinall(threads)
    # title_list = [thread.value for thread in threads]
    print('gevent result length:',len(title_list),title_list)
    return title_list

def run3():
    title_list = []
    th = []
    for url in url_list:
        t = Thread(target=get_title,args=(url,title_list))
        th.append(t)
        t.start()
    for t in th:
        t.join()
    print('threading result length:',len(title_list),title_list)
    return title_list


if __name__ == '__main__':
    t1 = timeit.timeit('run1()',setup="from __main__ import run1",number=1)
    print('sync time:',t1)
    t2 = timeit.timeit('run2()',setup="from __main__ import run2",number=1)
    print('gevent time:',t2)
    t3 = timeit.timeit('run3()',setup="from __main__ import run3",number=1)
    print('thread time:',t3)

djangoの下に置いてページ生成を表示する時間
同期:19065 ms gevent:1555 ms threading:166 ms
更新
python 3を追加します.5でasyncioテストを行い、geventとthreadingより少し長い1.1 s
async def get_title2():
    loop = asyncio.get_event_loop()
    title_list = []
    io = []
    for url in url_list:
        # get_title(url, title_list)
        io.append(loop.run_in_executor(None,get_title,url,title_list))
    for i in io:
        await i
    print(threading.current_thread(), 'Sync result length:', len(title_list), title_list)
    return title_list

def run4():
    loop = asyncio.get_event_loop()
    loop.run_until_complete(get_title2())
if __name__ == '__main__':
    global url_list
    url_list = get_url()
    t4 = timeit.timeit('run4()', setup="from __main__ import run4", number=1)
    print('thread time:', t4)