2012-05-18 16 views
20

Acabo de escribir un código simple para evaluar Redis + gevent para ver cómo async ayuda a perforamance y me sorprendió encontrar un mal rendimiento. aquí está mi código Si se deshace de las dos primeras líneas para aplicar el parche a este código, entonces verá el tiempo de "ejecución normal".redis + gevent - Rendimiento bajo: ¿qué estoy haciendo mal?

En un Ubuntu 12.04 LTS VM, estoy viendo un momento de

sin parche mono - 54 seg Con el parche mono - 61 segundos

¿Hay algo malo en mi código/enfoque? ¿Hay un problema de rendimiento aquí?

#!/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) 

Respuesta

47

Esto es esperado.

Ejecuta este punto de referencia en una máquina virtual, en la que el costo de las llamadas al sistema es mayor que en el hardware físico. Cuando se activa gevent, tiende a generar más llamadas al sistema (para manejar el dispositivo epoll), por lo que terminas con un menor rendimiento.

Puede verificar fácilmente este punto utilizando strace en el script.

Sin GEvent, el bucle interno genera:

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 

Con GEvent, tendrá apariciones 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 

Cuando la llamada recvfrom está bloqueando (EAGAIN), GEvent se remonta a la ciclo de eventos, por lo que se realizan llamadas adicionales para esperar los eventos de descriptores de archivos (epoll_wait).

Tenga en cuenta que este tipo de punto de referencia es el peor caso para cualquier sistema de bucle de eventos, porque solo tiene un descriptor de archivo, por lo que las operaciones de espera no se pueden factorizar en varios descriptores. Además, las E/S asincrónicas no pueden mejorar nada aquí ya que todo es sincrónico.

Es también un peor caso para Redis porque:

  • genera muchos de ida y vuelta al servidor

  • se conecta de forma sistemática/desconecta (1000 veces) porque la piscina se declara en función UxDomainSocket .

En realidad el punto de referencia no prueba GEvent, Redis o Redis-py: ejerce la capacidad de una máquina virtual para sostener un juego de ping-pong entre 2 procesos.

Si desea aumentar el rendimiento, es necesario:

  • utilizar la canalización para disminuir el número de idas y vueltas

  • hacer la piscina persistente en toda la referencia

Por ejemplo, considere con la siguiente secuencia de comandos:

#!/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) 

Con este script, obtengo un rendimiento 3 veces mejor y casi sin gastos generales con gevent.

+0

Gracias por la respuesta detallada. Si entiendo el problema más profundo, básicamente lo que he hecho es que solo hay un "objeto" que se puede esperar: si, por ejemplo, tuviera un conjunto de conexiones de Redis y utilizara gevent entonces me daría un mejor rendimiento (asumiendo redis puede mantenerse al día). Por cierto, la máquina virtual (y el conector Ux) era solo para pruebas. La producción será en instancias diferentes, etc., – vivekv

+0

si se utiliza tubería, luego cómo usar "bloqueo de redis" – Tallmad

Cuestiones relacionadas