redis + gevent - 表现不佳 - 我做错了什么?

viv*_*ekv 21 python performance redis gevent

我刚刚编写了一段简单的代码来测试Redis + gevent,看看async如何帮助完成性能,我很惊讶地发现性能不佳.这是我的代码.如果你摆脱前两行来修补这段代码,那么你将看到"正常执行"时间.

在Ubuntu 12.04 LTS 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)
Run Code Online (Sandbox Code Playgroud)

Did*_*zia 50

这是预料之中的.

您在VM上运行此基准测试,系统调用的成本高于物理硬件上的成本.当激活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
Run Code Online (Sandbox Code Playgroud)

有了gevent,你将会遇到:

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
Run Code Online (Sandbox Code Playgroud)

当recvfrom调用阻塞(EAGAIN)时,gevent返回到事件循环,因此执行其他调用以等待文件描述符事件(epoll_wait).

请注意,对于任何事件循环系统,这种基准测试都是最糟糕的情况,因为您只有一个文件描述符,因此等待操作不能分解为多个描述符.此外,异步I/O在这里无法改进,因为一切都是同步的.

这对Redis来说也是最糟糕的情况,因为:

  • 它会为服务器生成许多往返

  • 它系统地连接/断开连接(1000次),因为池是在UxDomainSocket函数中声明的.

实际上你的基准测试并没有测试gevent,redis或redis-py:它运行VM的能力来维持两个进程之间的乒乓游戏.

如果要提高性能,则需要:

  • 使用流水线技术减少往返次数

  • 使池在整个基准测试中保持不变

例如,请考虑使用以下脚本:

#!/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)
Run Code Online (Sandbox Code Playgroud)

使用这个脚本,我获得了大约3倍的性能,并且几乎没有gevent的开销.