【问题标题】:redis + gevent - Poor performance - what am I doing wrong?redis + gevent - 性能不佳 - 我做错了什么?
【发布时间】:2012-05-18 17:13:26
【问题描述】:

我刚刚写了一段简单的代码来对 Redis + gevent 进行性能测试,以了解异步如何帮助提高性能,我很惊讶地发现性能不佳。这是我的代码。如果您去掉前两行来猴子修补此代码,那么您将看到“正常执行”时间。

在 Ubuntu 12.04 LTS 虚拟机上,我看到了

没有猴子补丁 - 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)

【问题讨论】:

    标签: python performance redis gevent


    【解决方案1】:

    这是意料之中的。

    您在 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
    

    使用 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:它锻炼了虚拟机在两个进程之间维持乒乓球比赛的能力。

    如果你想提高性能,你需要:

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

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

    例如,考虑以下脚本:

    #!/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 的开销。

    【讨论】:

    • 感谢您的详细回复。如果我理解更深层次的问题,基本上我所做的是只有一个“对象”可以等待 - 例如,如果我有一个 Redis 连接池并且我使用 gevent 那么它会给我更好的性能(假设redis 可以跟上)。顺便说一句,VM(和 Ux 套接字)仅用于测试。生产将是不同的实例等,
    • 如果使用管道,那么如何使用“redis锁”
    猜你喜欢
    • 2017-09-29
    • 2019-06-15
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-25
    • 1970-01-01
    相关资源
    最近更新 更多