2015-02-06 40 views
10

Próbuję zapoznać się z asyncio, więc postanowiłem napisać klienta bazy danych. Jednak wydajność dokładnie pasuje do kodu synchronicznego. Jestem pewien, że to moje niezrozumienie koncepcji. Czy ktoś mógłby wyjaśnić, co robię?Wydajność asyncio

Proszę patrz przykład kodu poniżej:

class Connection: 
    def __init__(self, reader, writer, loop): 
     self.futures = deque() 

     # ... 

     self.reader_task = asyncio.async(self.recv_data(), loop=self.loop) 

    @asyncio.coroutine 
    def recv_data(self): 
     while 1: 
      try: 
       response = yield from self.reader.readexactly(4) 
       size, = struct.unpack('I', response) 
       response = yield from self.reader.readexactly(size) 

       # ...     

       future = self.futures.popleft() 

       if not future.cancelled(): 
        future.set_result(response) 

      except Exception: 
       break 

    def send_data(self, data): 
     future = asyncio.Future(loop=self.loop) 
     self.futures.append(future) 

     self.writer.write(data) 

     return future 


loop = asyncio.get_event_loop() 


@asyncio.coroutine 
def benchmark(): 
    connection = yield from create_connection(loop=loop, ...) 

    for i in range(10000): 
     yield from connection.send_data(...) 


s = time.monotonic() 

loop.run_until_complete(benchmark()) 

e = time.monotonic() 
print('Requests per second:', int(10000/(e - s))) 

Z góry dzięki.

Odpowiedz

12

Popełniłeś błąd w sposobie, w jaki dzwonisz pod numer send_data. Teraz, masz to:

@asyncio.coroutine 
def benchmark(): 
    connection = yield from create_connection(loop=loop, ...) 

    for i in range(10000): 
     yield from connection.send_data(...) 

Korzystając yield from wewnątrz pętli for, czekasz na future wracasz z send_data uzyskując wynik przed przejściem do następnej rozmowy. To sprawia, że ​​twój program jest zasadniczo synchroniczny. Chcesz, aby wszystkie połączenia do send_data i następnie czekać na wyniki:

@asyncio.coroutine 
def benchmark(): 
    connection = yield from create_connection(loop=loop, ...) 
    yield from asyncio.wait([connection.send_data(..) for _ in range(10000)]) 
+0

Idealne, dzięki. Z tego, co mogłem zrozumieć, tak samo jak tworzenie zadania dla każdego wywołania "send_data"? – Andrew

+2

@Andrew Mniej więcej, chociaż nadal musisz dodać kod do 'benchmark', aby poczekać na zakończenie każdego' zadania'. Właściwie, uważam, że wywołanie funkcji 'asyncio.wait' spowoduje, że wszystkie obiekty konturu zostaną przekazane do instancji' Task' wewnętrznie. – dano

+0

Tak, obaj jesteście poprawni. 'asyncio.wait' zawija każdy przekazany obiekt typu coroutine lub jest oczekiwany w przyszłości' Task'. Samotny akt owijania ich opcją 'loop.create_task' lub' asyncio.ensure_future' może zaplanować je w pętli, ale nie blokuje wykonania kodu korynkowego, gdy w końcu się kończą.Wciąż będziesz musiał "wydać z' 'zadania' lub przekazać je do czegoś takiego jak 'asyncio.wait'. –

3

Pyton asyncio moduł jest pojedynczy gwintowany:

Moduł ten zapewnia infrastrukturę do pisania jednowątkowego kod współbieżne przy użyciu współprogram, multipleksowania I/O dostęp do ponad gniazd i innych zasobów, bieganie klientów sieciowych i serwerów, a inne powiązane prymitywy.

This question ma wyjaśnienia dlaczego asyncio może być wolniejszy niż gwintowanie, ale w skrócie: asyncio wykorzystuje pojedynczy wątek, aby wykonać swój kod, więc nawet jeśli masz kilka współprogram, wszystkie one wykonywać seryjnie. Pula wątków służy do wykonywania niektórych wywołań zwrotnych i operacji we/wy. Ze względu na GIL, wątkowanie wykonuje również szeregowo kod użytkownika, chociaż operacje We/Wy mogą być uruchamiane synchronicznie.

Powód używania asyncio nie poprawia kodu seryjnego, ponieważ pętla zdarzeń działa tylko raz na raz.

+5

Kod PO powinna nadal działać lepiej niż kod synchroniczny, bo to I/O związany. Nie ma znaczenia, że ​​istnieje jeden wątek - podczas gdy operacje we/wy są wykonywane w jednym korku, inne kleryny mogą być wykonywane. Pytanie, z którym się łączysz, jest w pewnym szczególnym przypadku - używało 'getaddrinfo', które nie jest zaimplementowane przy użyciu asynchronicznych operacji we/wy. Zamiast niego używa małego "ThreadPool", który ogranicza ilość dostępnego równoległości. To spowodowało, że był wolniejszy niż zwykły wielowątkowy kod, ale nadal byłby szybszy niż kod synchroniczny, o to właśnie chodzi. – dano

+1

@dano Mój błąd. Nie rozumiałem tego wystarczająco dobrze. Głosuję na twoją odpowiedź. – zstewart

+1

Bez problemu. Asynchroniczne frameworki to dość dziwna koncepcja, która pozwala ci się otoczyć. Ostatnie zdanie, które napisałeś w swojej odpowiedzi, jest właściwie poprawne, ale działo się tak z powodu błędu kodowania, a nie ograniczenia "asyncio". – dano

Powiązane problemy