2012-05-18 7 views
20

Ich habe gerade einen einfachen Code geschrieben, um Redis + gevent zu testen, um zu sehen, wie Async zur Perforation beiträgt, und ich war überrascht, eine schlechte Leistung zu finden. Hier ist mein Code. Wenn Sie die ersten beiden Zeilen entfernen, um diesen Code zu affen, sehen Sie das Timing "normale Ausführung".redis + gevent - Schlechte Leistung - was mache ich falsch?

Auf einer Ubuntu 12.04 LTS VM, ich bin ein Timing von

ohne Affen Patch zu sehen - 54 Sekunden mit Affen-Patch - 61 Sekunden

Gibt es etwas falsch mit meinem Code/Ansatz? Gibt es hier ein Perf-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) 

Antwort

47

Dies wird erwartet.

Sie führen diesen Benchmark auf einer VM aus, auf der die Kosten für Systemaufrufe höher sind als auf physischer Hardware. Wenn gevent aktiviert ist, erzeugt es tendenziell mehr Systemaufrufe (um das epoll-Gerät zu handhaben), so dass Sie weniger Leistung haben.

Sie können diesen Punkt leicht überprüfen, indem Sie strace im Skript verwenden.

Ohne GEVENT, die innere Schleife erzeugt:

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 

Mit GEVENT, haben Sie Vorkommen von:

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 

Wenn der Recvfrom Anruf (EAGAIN) blockiert, GEVENT geht an die Rückseite Ereignisschleife, so dass zusätzliche Aufrufe ausgeführt werden, um auf Dateibeschreibungs-Ereignisse (epoll_wait) zu warten.

Bitte beachten Sie, dass diese Art von Benchmark ein Worst Case für jedes Ereignisschleifensystem ist, da Sie nur einen Dateideskriptor haben, so dass die Warteoperationen nicht auf mehrere Deskriptoren faktorisiert werden können. Darüber hinaus können Async-I/Os hier nichts verbessern, da alles synchron ist.

Es ist auch eine Worst-Case für Redis, weil:

  • es viele Roundtrips zum Server

  • verbindet erzeugt systematisch/trennt (1000-mal), da der Pool in UxDomainSocket Funktion deklariert wird .

Eigentlich ist Ihre Benchmark nicht testen GEVENT, redis oder redis-py: es übt die Fähigkeit einer VM ein Ping-Pong-Spiel zwischen zwei Prozessen zu unterstützen.

Wenn Sie die Leistung erhöhen wollen, müssen Sie:

  • Pipelining die Anzahl der Roundtrips

  • der Pool machen hartnäckig über den gesamten Benchmark verringern

Betrachten Sie zum Beispiel das folgende Skript:

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

Mit diesem Skript bekomme ich etwa 3x bessere Leistung und fast keinen Overhead mit gevent.

+0

Danke für die ausführliche Antwort. Wenn ich das tiefere Problem im Grunde verstehe, was ich getan habe, ist, dass es nur ein "Objekt" gibt, auf das gewartet werden kann - wenn ich zum Beispiel einen Pool von Redis-Verbindungen hatte und ich gevent verwende, dann würde es mir die bessere Leistung geben redis kann mithalten). BTW die VM (und Ux-Buchse) war nur zum Testen. Produktion wird verschiedene Instanzen usw., – vivekv

+0

wenn Pipeline verwendet wird, dann wie "Redis Sperre" verwenden – Tallmad