geventsとgreenletは初めてです。私はそれらをどのように使用するかについての良いドキュメントを見つけましたが、グリーンレットをいつどのように使用するべきかについて正当化するものはありませんでした!
- 彼らは本当に何が得意ですか?
- プロキシサーバーで使用するかどうかは良い考えですか?
- なぜスレッドではないのですか?
それらが基本的にコルーチンである場合、それらがどのようにして並行性を提供できるかについて、私は確信がありません。
geventsとgreenletは初めてです。私はそれらをどのように使用するかについての良いドキュメントを見つけましたが、グリーンレットをいつどのように使用するべきかについて正当化するものはありませんでした!
それらが基本的にコルーチンである場合、それらがどのようにして並行性を提供できるかについて、私は確信がありません。
回答:
Greenletは並行性を提供しますが、並列性は提供しません。並行性とは、コードが他のコードから独立して実行できる場合です。並列処理とは、並行コードを同時に実行することです。並列処理は、ユーザー空間で実行する必要のある作業が多く、通常はCPUに負荷がかかる場合に特に役立ちます。並行性は問題を分解するのに役立ち、異なる部分をより簡単に並行してスケジュールおよび管理できるようにします。
Greenletは、1つのソケットとの相互作用が他のソケットとの相互作用とは無関係に発生するネットワークプログラミングで本当に輝いています。これは同時実行性の典型的な例です。各Greenletは独自のコンテキストで実行されるため、スレッド化せずに同期APIを引き続き使用できます。スレッドは仮想メモリとカーネルのオーバーヘッドの点で非常に高価であるため、これは良いことです。したがって、スレッドで実現できる同時実行性は大幅に低下します。さらに、Pythonでのスレッド化は、GILが原因で、通常よりもコストがかかり、制限も多くなります。並行性の代替手段は通常、Twisted、libevent、libuv、node.jsなどのプロジェクトで、すべてのコードが同じ実行コンテキストを共有し、イベントハンドラーを登録します。
リクエストの処理は独立して実行でき、そのように記述する必要があるため、プロキシの記述には(geventなどの適切なネットワークサポートを備えた)グリーンレットを使用することをお勧めします。
Greenletは、先ほど説明した理由により同時実行性を提供します。並行性は並列処理ではありません。イベントの登録を隠し、通常は現在のスレッドをブロックする呼び出しに対してスケジューリングを実行することにより、geventのようなプロジェクトは、非同期APIを変更することなくこの並行性を公開し、システムのコストを大幅に削減します。
import hashlib def checksum_md5(filename): md5 = hashlib.md5() with open(filename,'rb') as f: for chunk in iter(lambda: f.read(8192), b''): md5.update(chunk) return md5.digest()
@Maxの答えを取り入れ、スケーリングに関連性を追加すると、違いがわかります。これを実現するには、入力するURLを次のように変更します。
URLS_base = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
URLS = []
for _ in range(10000):
for url in URLS_base:
URLS.append(url)
500になる前に落ちたマルチプロセスバージョンを削除する必要がありました。しかし、10,000回の反復で:
Using gevent it took: 3.756914
-----------
Using multi-threading it took: 15.797028
したがって、geventを使用したI / Oにはいくつかの大きな違いがあることがわかります
上記の@TemporalBeingの回答を修正すると、グリーンレットはスレッドよりも「高速」ではなく、並行性の問題を解決するために60000スレッドを生成するのは不適切なプログラミング手法であり、代わりに小さなスレッドのプールが適切です。これは、より合理的な比較です(このSOの投稿を引用している人々への返答としての私のredditの投稿から)。
import gevent
from gevent import socket as gsock
import socket as sock
import threading
from datetime import datetime
def timeit(fn, URLS):
t1 = datetime.now()
fn()
t2 = datetime.now()
print(
"%s / %d hostnames, %s seconds" % (
fn.__name__,
len(URLS),
(t2 - t1).total_seconds()
)
)
def run_gevent_without_a_timeout():
ip_numbers = []
def greenlet(domain_name):
ip_numbers.append(gsock.gethostbyname(domain_name))
jobs = [gevent.spawn(greenlet, domain_name) for domain_name in URLS]
gevent.joinall(jobs)
assert len(ip_numbers) == len(URLS)
def run_threads_correctly():
ip_numbers = []
def process():
while queue:
try:
domain_name = queue.pop()
except IndexError:
pass
else:
ip_numbers.append(sock.gethostbyname(domain_name))
threads = [threading.Thread(target=process) for i in range(50)]
queue = list(URLS)
for t in threads:
t.start()
for t in threads:
t.join()
assert len(ip_numbers) == len(URLS)
URLS_base = ['www.google.com', 'www.example.com', 'www.python.org',
'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
for NUM in (5, 50, 500, 5000, 10000):
URLS = []
for _ in range(NUM):
for url in URLS_base:
URLS.append(url)
print("--------------------")
timeit(run_gevent_without_a_timeout, URLS)
timeit(run_threads_correctly, URLS)
結果は次のとおりです。
--------------------
run_gevent_without_a_timeout / 30 hostnames, 0.044888 seconds
run_threads_correctly / 30 hostnames, 0.019389 seconds
--------------------
run_gevent_without_a_timeout / 300 hostnames, 0.186045 seconds
run_threads_correctly / 300 hostnames, 0.153808 seconds
--------------------
run_gevent_without_a_timeout / 3000 hostnames, 1.834089 seconds
run_threads_correctly / 3000 hostnames, 1.569523 seconds
--------------------
run_gevent_without_a_timeout / 30000 hostnames, 19.030259 seconds
run_threads_correctly / 30000 hostnames, 15.163603 seconds
--------------------
run_gevent_without_a_timeout / 60000 hostnames, 35.770358 seconds
run_threads_correctly / 60000 hostnames, 29.864083 seconds
PythonでのノンブロッキングIOについて誰もが誤解しているのは、Pythonインタープリターが、ネットワーク接続自体がIOを返すよりも大規模にソケットから結果を取得する作業に対応できるという信念です。これは確かに一部のケースでは当てはまりますが、Pythonインタープリターは本当に非常に遅いため、人々が考えるほど頻繁には当てはまりません。ここの私のブログ投稿では、非常に単純なものでも、データベースやDNSサーバーなどへの鮮明で高速なネットワークアクセスを処理している場合、これらのサービスはPythonコードよりもはるかに速く戻ることができることを示すいくつかのグラフィカルプロファイルを示しています何千もの接続に参加できます。
これは分析するのに十分興味深いものです。以下は、グリーンレットとマルチプロセッシングプールとマルチスレッドのパフォーマンスを比較するコードです。
import gevent
from gevent import socket as gsock
import socket as sock
from multiprocessing import Pool
from threading import Thread
from datetime import datetime
class IpGetter(Thread):
def __init__(self, domain):
Thread.__init__(self)
self.domain = domain
def run(self):
self.ip = sock.gethostbyname(self.domain)
if __name__ == "__main__":
URLS = ['www.google.com', 'www.example.com', 'www.python.org', 'www.yahoo.com', 'www.ubc.ca', 'www.wikipedia.org']
t1 = datetime.now()
jobs = [gevent.spawn(gsock.gethostbyname, url) for url in URLS]
gevent.joinall(jobs, timeout=2)
t2 = datetime.now()
print "Using gevent it took: %s" % (t2-t1).total_seconds()
print "-----------"
t1 = datetime.now()
pool = Pool(len(URLS))
results = pool.map(sock.gethostbyname, URLS)
t2 = datetime.now()
pool.close()
print "Using multiprocessing it took: %s" % (t2-t1).total_seconds()
print "-----------"
t1 = datetime.now()
threads = []
for url in URLS:
t = IpGetter(url)
t.start()
threads.append(t)
for t in threads:
t.join()
t2 = datetime.now()
print "Using multi-threading it took: %s" % (t2-t1).total_seconds()
結果は次のとおりです。
Using gevent it took: 0.083758
-----------
Using multiprocessing it took: 0.023633
-----------
Using multi-threading it took: 0.008327
greenletは、マルチスレッドライブラリとは異なり、GILに拘束されないと主張していると思います。さらに、Greenlet docは、ネットワーク運用を目的としていると述べています。ネットワーク集中型の操作の場合、スレッド切り替えは問題なく、マルチスレッドアプローチがかなり高速であることがわかります。また、常にpythonの公式ライブラリを使用することをお勧めします。GreenletをWindowsにインストールしようとしたところ、dll依存関係の問題が発生したため、このテストをLinux vmで実行しました。常に、どのマシンでも実行できるようにコードを記述してください。
getsockbyname
OSレベルで結果をキャッシュすることに注意してください(少なくとも私のマシンでは)。以前は不明または期限切れのDNSで呼び出された場合、実際にはネットワーククエリを実行しますが、これには時間がかかる場合があります。最近解決されたばかりのホスト名で呼び出されると、応答がはるかに速く返されます。したがって、ここでは測定方法に欠陥があります。これはあなたの奇妙な結果を説明します-geventは実際にはマルチスレッドよりもはるかに悪いことはあり得ません-どちらもVMレベルでは実際には並列ではありません。
using_gevent() 421.442985535ms using_multiprocessing() 394.540071487ms using_multithreading() 402.48298645ms