Подтвердить что ты не робот

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)
4b9b3361

Ответ 1

Ожидается.

Вы запускаете этот тест на виртуальной машине, на которой стоимость системных вызовов выше, чем на физическом оборудовании. Когда gevent активирован, он имеет тенденцию генерировать больше системных вызовов (для обработки устройства epoll), поэтому вы оказываетесь в меньшей производительности.

Вы можете легко проверить эту точку, используя strace на script.

Без 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) выполняются дополнительные вызовы.

Обратите внимание, что этот тип тестов является наихудшим случаем для любой системы циклов событий, потому что у вас есть только один файловый дескриптор, поэтому операции ожидания не могут быть факторизованы на нескольких дескрипторах. Кроме того, асинхронные входы/выходы здесь ничего не могут улучшить, поскольку все синхронно.

Это также худший случай для Redis, потому что:

  • он генерирует много обращений к серверу

  • систематически подключается/отключается (1000 раз), потому что пул объявлен в функции UxDomainSocket.

На самом деле ваш тест не тестирует gevent, redis или redis-py: он использует способность виртуальной машины поддерживать игру в пинг-понг между двумя процессами.

Если вы хотите повысить производительность, вам необходимо:

  • используйте конвейерную линию для уменьшения количества обращений в оба конца

  • сделать пул постоянным по всему эталону

Например, рассмотрите следующие script:

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

С этим script я получаю примерно 3-кратную производительность и почти не накладные расходы с gevent.