Dette forventes.
Du kører dette benchmark på en VM, hvor omkostningerne ved systemopkald er højere end på fysisk hardware. Når gevent er aktiveret, har det en tendens til at generere flere systemopkald (for at håndtere epoll-enheden), så du ender med mindre ydeevne.
Du kan nemt kontrollere dette punkt ved at bruge strace på scriptet.
Uden gavt genererer den indre sløjfe:
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
Med gevent vil du have forekomster af:
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
Når recvfrom-kaldet blokerer (EAGAIN), går gevent tilbage til hændelsesløkken, så yderligere opkald udføres for at vente på filbeskrivelseshændelser (epoll_wait).
Bemærk venligst, at denne type benchmark er et worst case for ethvert hændelsesløkkesystem, fordi du kun har én filbeskrivelse, så venteoperationerne kan ikke faktoriseres på flere deskriptorer. Desuden kan asynkrone I/O'er ikke forbedre noget her, da alt er synkront.
Det er også et worst case for Redis, fordi:
-
det genererer mange rundrejser til serveren
-
den forbinder/afbryder systematisk (1000 gange), fordi puljen er deklareret i UxDomainSocket-funktionen.
Faktisk tester dit benchmark ikke gevent, redis eller redis-py:det udøver en VM's evne til at opretholde et ping-pong-spil mellem 2 processer.
Hvis du vil øge ydeevnen, skal du:
-
brug pipelining til at reducere antallet af rundrejser
-
gøre poolen vedvarende på tværs af hele benchmark
Overvej for eksempel med følgende script:
#!/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)
Med dette script får jeg omkring 3 gange bedre ydeevne og næsten ingen overhead med givet.