Dies wird erwartet.
Sie führen diesen Benchmark auf einer VM aus, auf der die Kosten für Systemaufrufe höher sind als auf physischer Hardware. Wenn gevent aktiviert ist, generiert es tendenziell mehr Systemaufrufe (um das epoll-Gerät zu handhaben), sodass Sie am Ende weniger Leistung haben.
Sie können diesen Punkt leicht überprüfen, indem Sie strace im Skript verwenden.
Ohne gevent generiert die innere Schleife:
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
Mit gevent treten folgende Ereignisse auf:
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
Wenn der recvfrom-Aufruf blockiert (EAGAIN), kehrt gevent zur Ereignisschleife zurück, sodass zusätzliche Aufrufe ausgeführt werden, um auf Dateideskriptorereignisse zu warten (epoll_wait).
Bitte beachten Sie, dass diese Art von Benchmark der schlimmste Fall für jedes Event-Loop-System ist, da Sie nur einen Dateideskriptor haben, sodass die Warteoperationen nicht auf mehrere Deskriptoren faktorisiert werden können. Außerdem können asynchrone I/Os hier nichts verbessern, da alles synchron ist.
Es ist auch ein Worst Case für Redis, weil:
-
es generiert viele Roundtrips zum Server
-
es verbindet/trennt systematisch (1000 mal), weil der Pool in der UxDomainSocket-Funktion deklariert ist.
Tatsächlich testet Ihr Benchmark nicht gevent, redis oder redis-py:Er übt die Fähigkeit einer VM aus, ein Ping-Pong-Spiel zwischen zwei Prozessen aufrechtzuerhalten.
Wenn Sie die Leistung steigern möchten, müssen Sie:
-
Verwenden Sie Pipelining, um die Anzahl der Roundtrips zu verringern
-
den Pool über den gesamten Benchmark hinweg persistent machen
Betrachten Sie zum Beispiel das folgende Skript:
#!/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)
Mit diesem Skript bekomme ich eine etwa 3-mal bessere Leistung und fast keinen Overhead mit gevent.