Вопрос по python, performance – redis + gevent - низкая производительность - что я делаю не так?

21

Я только что написал простой код для тестирования Redis + Gevent, чтобы увидеть, как async помогает производительности, и я был удивлен, обнаружив плохую производительность. вот мой код Если вы избавитесь от первых двух строк, исправляющих этот код, то увидите «нормальное выполнение». время.

На ВМ 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)

Ваш Ответ

1   ответ
50

Это ожидается.

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

  • it generates many roundtrips to the server

  • it systematically connects/disconnects (1000 times) because the pool is declared in UxDomainSocket function.

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

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

  • use pipelining to decrease the number of roundtrips

  • make the pool persistent across the whole benchmark

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

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

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded vivekv

Похожие вопросы