2012-05-18 93 views
20

我刚写了一段简单的代码来测试Redis + gevent以查看异步如何帮助性能,我惊讶地发现性能不佳。这是我的代码。如果你摆脱了前两行猴子补丁这个代码,那么你会看到“正常执行”的时机。redis + gevent - 糟糕的表现 - 我做错了什么?

在Ubuntu的LTS 12.04 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) 

回答

47

这是预期的。

您运行在虚拟机上这一基准,在其系统调用的成本比在物理硬件上更高。当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,你将有OCCURENCES:

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)。

请注意这种基准为任何事件回路系统中的最坏的情况,因为你只能有一个文件描述符,所以等待操作不能在几个描述被因式分解。此外,由于所有内容都是同步的,异步I/O在这里无法改善。

这也是Redis的最坏的情况,因为:

  • 它会产生很多往返于服务器

  • 系统地连接/断开(1000倍),因为池在UxDomainSocket函数声明。

其实你的基准不测试GEVENT,Redis的或Redis的-PY:它行使一个虚拟机的能力,以维持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的开销。

+0

感谢您的详细回复。如果我理解了更深层次的问题,基本上我所做的是只有一个可以等待的“对象” - 例如,如果我有一个Redis连接池,并且使用gevent,那么它会给我更好的性能(假设redis可以跟上)。顺便说一句,虚拟机(和Ux套接字)仅用于测试。生产将会是不同的实例等,如果使用管道,则 – vivekv

+0

,那么如何使用“redis锁定” – Tallmad