redis + gevent - Плохая производительность - что я делаю не так?

Я только что написал простой фрагмент кода для проверки производительности 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)

person vivekv    schedule 18.05.2012    source источник


Ответы (1)


Это ожидаемо.

Вы запускаете этот тест на виртуальной машине, на которой стоимость системных вызовов выше, чем на физическом оборудовании. Когда 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).

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

Это также худший случай для 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.

person Didier Spezia    schedule 19.05.2012
comment
Спасибо за подробный ответ. Если я понимаю более глубокую проблему, в основном то, что я сделал, это то, что есть только один объект, который можно ожидать - если, например, у меня есть пул соединений Redis, и я использую gevent, тогда это дало бы мне лучшую производительность (при условии, что Redis может поддерживать). Кстати, виртуальная машина (и сокет Ux) предназначалась только для тестирования. Производство будет различных экземпляров и т. д., - person vivekv; 20.05.2012
comment
если используется конвейер, то как использовать блокировку Redis - person Tallmad; 27.03.2013