2012-05-18 14 views
20

Właśnie napisałem prosty kawałek kodu do perfekcyjnego testu Redis + gevent, aby zobaczyć, jak asynchronicznie pomaga perforamance i byłem zaskoczony, że znalazłem złą wydajność. tutaj jest mój kod. Jeśli pozbędziesz się pierwszych dwóch linii, aby przerobić ten kod na małpy, zobaczysz czas "normalnej realizacji".redis + gevent - Słaba wydajność - co robię źle?

Na Ubuntu 12.04 LTS VM, ja widząc taktowanie

bez małpa patch - 54 sek z małpa patch - 61 sekund

Czy jest coś nie tak z moim kodem/podejście? Czy jest tu jakiś problem?

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

Odpowiedz

47

To jest oczekiwane.

Ten test porównawczy jest uruchamiany na maszynie wirtualnej, na której koszt wywołań systemowych jest wyższy niż na sprzęcie fizycznym. Kiedy gevent jest aktywowany, ma tendencję do generowania większej liczby wywołań systemowych (w celu obsługi urządzenia epoll), dzięki czemu uzyskuje się mniejszą wydajność.

Możesz łatwo sprawdzić ten punkt, używając strace w skrypcie.

Bez gevent, wewnętrzna pętla generuje:

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 

Z gevent, trzeba będzie wystąpień:

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 

Kiedy rozmowa recvfrom blokuje (EAGAIN) gevent sięga pętli zdarzeń, więc wykonywane są dodatkowe wywołania, aby czekać na zdarzenia deskryptorów plików (epoll_wait).

Należy zwrócić uwagę, że ten rodzaj testu porównawczego jest najgorszym przypadkiem dla dowolnego systemu pętli zdarzeń, ponieważ istnieje tylko jeden deskryptor pliku, więc operacje oczekiwania nie mogą być uwzględniane w kilku deskryptorach. Ponadto asynchroniczne operacje we/wy nie mogą niczego poprawić, ponieważ wszystko jest synchroniczne.

Jest to również najgorszy przypadek Redis, ponieważ:

  • generuje wiele roundtrips do serwera

  • systematycznie łączy/rozłącza (1000 razy), ponieważ basen jest zadeklarowana w funkcji UxDomainSocket .

Właściwie Twój odniesienia nie testuje gevent, Redis lub Redis-Py: sprawowanej zdolność VM aby utrzymać grę ping-pong między 2 procesów.

Jeśli chcesz, aby zwiększyć wydajność, trzeba:

  • użycie rurociąg, aby zmniejszyć liczbę powrotu z

  • zrobić basen trwałe w całym teście

Na przykład rozważmy poniższy skrypt:

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

Dzięki temu skryptowi uzyskuję około 3 razy lepszą wydajność i prawie żadnych kosztów ogólnych przy użyciu gevent.

+0

Dzięki za szczegółową odpowiedź. Jeśli rozumiem głębszy problem, w zasadzie to, co zrobiłem, to to, że istnieje tylko jeden "obiekt", na który można czekać - jeśli na przykład miałem pulę połączeń Redis i używam geventa, to dałbym mi lepszą wydajność (zakładając redis może nadążyć). BTW VM (i gniazdo Ux) służy wyłącznie do testowania. Produkcją będą inne instancje itp., – vivekv

+0

jeśli używany jest potok, to jak używać "blokady redis" – Tallmad

Powiązane problemy