Isso é esperado.
Você executa esse benchmark em uma VM, na qual o custo das chamadas do sistema é maior do que no hardware físico. Quando o gevent é ativado, ele tende a gerar mais chamadas de sistema (para lidar com o dispositivo epoll), então você acaba com menos desempenho.
Você pode facilmente verificar este ponto usando strace no script.
Sem gevent, o loop interno gera:
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
Com o gevent, você terá ocorrências de:
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
Quando a chamada recvfrom está bloqueando (EAGAIN), gevent volta ao loop de eventos, então chamadas adicionais são feitas para aguardar eventos de descritores de arquivo (epoll_wait).
Observe que esse tipo de benchmark é o pior caso para qualquer sistema de loop de eventos, porque você tem apenas um descritor de arquivo, portanto, as operações de espera não podem ser fatoradas em vários descritores. Além disso, as E/Ss assíncronas não podem melhorar nada aqui, pois tudo é síncrono.
Também é o pior caso para o Redis porque:
-
gera muitas viagens de ida e volta para o servidor
-
ele conecta/desconecta sistematicamente (1000 vezes) porque o pool é declarado na função UxDomainSocket.
Na verdade, seu benchmark não testa gevent, redis ou redis-py:ele exercita a capacidade de uma VM de sustentar um jogo de pingue-pongue entre 2 processos.
Se você deseja aumentar o desempenho, você precisa:
-
use pipelining para diminuir o número de viagens de ida e volta
-
tornar o pool persistente em todo o benchmark
Por exemplo, considere com o seguinte 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)
Com esse script, obtenho desempenho cerca de 3x melhor e quase nenhuma sobrecarga com o gevent.