21

Redis + gevent のパフォーマンス テストを行う簡単なコードを書いて、非同期がパフォーマンスにどのように役立つかを確認しましたが、パフォーマンスが悪いことに驚きました。ここに私のコードがあります。このコードにモンキー パッチを適用するために最初の 2 行を削除すると、「通常の実行」のタイミングが表示されます。

Ubuntu 12.04 LTS VM で、次のタイミングが発生しています。

モンキー パッチなし - 54 秒 モンキー パッチあり - 61 秒

私のコード/アプローチに何か問題がありますか? ここにパフォーマンスの問題はありますか?

#!/usr/bin/python

from gevent import monkey

monkey.patch_all()

import timeit
import redis
from redis.connection import UnixDomainSocketConnection

def UxDomainSocket():
    pool = redis.ConnectionPool(connection_class=UnixDomainSocketConnection, path =    '/var/redis/redis.sock')
    r = redis.Redis(connection_pool = pool)
    r.set("testsocket", 1)
    for i in range(100):
            r.incr('testsocket', 10)
    r.get('testsocket')
    r.delete('testsocket')


print timeit.Timer(stmt='UxDomainSocket()',
 setup='from __main__ import UxDomainSocket').timeit(number=1000)
4

1 に答える 1

52

これは予想されます。

このベンチマークは、システムコールのコストが物理ハードウェアよりも高いVMで実行します。geventをアクティブにすると、(epollデバイスを処理するために)より多くのシステムコールが生成される傾向があるため、パフォーマンスが低下します。

スクリプトでstraceを使用すると、この点を簡単に確認できます。

geventがない場合、内部ループは以下を生成します。

recvfrom(3, ":931\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41
recvfrom(3, ":941\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41

geventを使用すると、次のことが発生します。

recvfrom(3, ":221\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41
recvfrom(3, 0x7b0f04, 4096, 0, 0, 0)    = -1 EAGAIN (Resource temporarily unavailable)
epoll_ctl(5, EPOLL_CTL_ADD, 3, {EPOLLIN, {u32=3, u64=3}}) = 0
epoll_wait(5, {{EPOLLIN, {u32=3, u64=3}}}, 32, 4294967295) = 1
clock_gettime(CLOCK_MONOTONIC, {2469, 779710323}) = 0
epoll_ctl(5, EPOLL_CTL_DEL, 3, {EPOLLIN, {u32=3, u64=3}}) = 0
recvfrom(3, ":231\r\n", 4096, 0, NULL, NULL) = 6
sendto(3, "*3\r\n$6\r\nINCRBY\r\n$10\r\ntestsocket\r"..., 41, 0, NULL, 0) = 41

recvfrom呼び出しがブロックされている場合(EAGAIN)、geventはイベントループに戻るため、ファイル記述子イベント(epoll_wait)を待機するために追加の呼び出しが行われます。

この種のベンチマークは、ファイル記述子が1つしかないため、待機操作を複数の記述子で因数分解できないため、イベントループシステムにとって最悪のケースであることに注意してください。さらに、すべてが同期しているため、非同期I/Oはここでは何も改善できません。

これは、Redisにとっても最悪のケースです。理由は次のとおりです。

  • サーバーへの多くのラウンドトリップを生成します

  • プールはUxDomainSocket関数で宣言されているため、体系的に接続/切断(1000回)します。

実際、ベンチマークはgevent、redis、またはredis-pyをテストしません。VMの機能を実行して、2つのプロセス間でピンポンゲームを維持します。

パフォーマンスを向上させたい場合は、次のことを行う必要があります。

  • パイプラインを使用して、ラウンドトリップの数を減らします

  • ベンチマーク全体でプールを永続化する

たとえば、次のスクリプトを検討してください。

#!/usr/bin/python

from gevent import monkey
monkey.patch_all()

import timeit
import redis
from redis.connection import UnixDomainSocketConnection

pool = redis.ConnectionPool(connection_class=UnixDomainSocketConnection, path = '/tmp/redis.sock')

def UxDomainSocket():
    r = redis.Redis(connection_pool = pool)
    p = r.pipeline(transaction=False)
    p.set("testsocket", 1)
    for i in range(100):
        p.incr('testsocket', 10)
    p.get('testsocket')
    p.delete('testsocket')
    p.execute()

print timeit.Timer(stmt='UxDomainSocket()', setup='from __main__ import UxDomainSocket').timeit(number=1000)

このスクリプトを使用すると、パフォーマンスが約3倍向上し、geventによるオーバーヘッドはほとんどありません。

于 2012-05-19T08:08:20.920 に答える