Redis + Gevent - 性能差 - 我做错了什么?

22

我刚刚编写了一段简单的代码,用于对Redis + gevent进行性能测试,以了解异步如何提高性能。但是我惊讶地发现表现不佳。这是我的代码。如果你去掉前两行代码,取消monkey patching操作,你会看到“正常执行”的时间。

在Ubuntu 12.04 LTS虚拟机上,我看到的时间是:

没有monkey patch - 54秒 使用monkey patch - 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)
1个回答

55

这是预期的结果。

您在虚拟机上运行此基准测试,其中系统调用的成本高于物理硬件。当激活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时,您将遇到以下情况:

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:它展示了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)

使用这个脚本,我可以在使用 gevent 时获得大约3倍的性能提升,并且几乎没有任何开销。


感谢您详细的回复。如果我理解得更深入一些,基本上我所做的就是只有一个“对象”可以等待 - 例如,如果我有一个 Redis 连接池并且我使用 gevent,那么它会给我更好的性能(假设 Redis 可以跟上)。顺便说一下,VM(和 Ux socket)仅用于测试。生产环境将是不同的实例等。 - vivekv
如果使用流水线,那么如何使用 "redis lock"? - Tallmad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接