2015-02-06 11 views
10

Sto cercando di familiarizzare con asyncio, quindi ho deciso di scrivere un client di database. Tuttavia, le prestazioni corrispondono esattamente al codice sincrono. Sono sicuro che questo è il mio fraintendimento di un concetto. Qualcuno potrebbe spiegare cosa sto facendo wriong?Prestazioni di asyncio

Si prega di vedere l'esempio di codice qui sotto:

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))) 

Grazie in anticipo.

risposta

12

Hai fatto un errore nel modo in cui stai chiamando send_data. In questo momento, hai questo:

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

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

Utilizzando yield from all'interno del ciclo for, si sta aspettando la future si sta tornando da send_data a dare un risultato prima di passare alla successiva chiamata. Questo rende il tuo programma fondamentalmente sincrono. Si vuole fare tutte le chiamate a send_data, e poi attesa per i risultati:

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

Perfetto, grazie. Da quello che potrei capire è come creare un'attività per ogni chiamata "send_data"? – Andrew

+2

@Andrew Più o meno, anche se avresti ancora bisogno di aggiungere il codice al benchmark per attendere il completamento di ogni 'Task'. In realtà, credo che la chiamata a 'asyncio.wait' trasformerà tutti gli oggetti di coroutine passati ad esso in istanze di' Task' internamente, comunque. – dano

+0

Sì, siete entrambi corretti. 'asyncio.wait' avvolgerà qualsiasi oggetto passato in coroutine o attendibile in un futuro' Task'. L'atto solitario di avvolgerli con 'loop.create_task' o' asyncio.ensure_future' può programmarli nel ciclo, ma non blocca l'esecuzione del codice di coroutine mentre alla fine finisce.Dovresti comunque "cedere" a quelle "Task" o passarle a qualcosa come "asyncio.wait". –

3

Il pitone modulo di asyncio è filettato unico:

Questo modulo fornisce l'infrastruttura per la scrittura di codice concorrente single-threaded con coroutine, multiplexing/O l'accesso tramite prese e altre risorse che, in esecuzione client e server di rete, e altri primitivi correlati.

This question ha una spiegazione del motivo per cui asyncio può essere più lento di threading, ma in breve: asyncio utilizza un singolo thread per eseguire il codice, quindi, anche se si dispone di più coroutine, tutti esecuzione in serie. Un pool di thread viene utilizzato per eseguire alcuni callback e I/O. A causa di GIL, il threading esegue anche il codice utente in serie, sebbene le operazioni di I/O possano essere eseguite in modo sincrono.

Il motivo dell'utilizzo di asyncio non offre un miglioramento rispetto al codice eseguito in serie, poiché il ciclo di eventi esegue solo una coroutine alla volta.

+5

Il codice PO dovrebbe essere ancora un rendimento migliore rispetto al codice sincrono, perché è legato I/O. Non importa che ci sia un singolo thread - mentre I/O è in esecuzione in una coroutine, possono essere eseguite altre coroutine. La domanda a cui ti sei collegato è un caso un po 'speciale: utilizzava 'getaddrinfo', che in realtà non è implementato usando l'I/O asincrono. Usa invece un piccolo 'ThreadPool', che limita la quantità di parallelismo disponibile. Ciò lo ha reso più lento del normale codice multi-thread, ma sarebbe comunque più veloce del codice sincrono, che è ciò di cui tratta questa domanda. – dano

+1

@dano Il mio errore allora. Non l'ho capito abbastanza bene. Sto votando la tua risposta. – zstewart

+1

Nessun problema. I quadri asincroni sono un concetto abbastanza strano da avvolgere. L'ultima frase che hai scritto nella tua risposta è in realtà sostanzialmente corretta, ma stava accadendo a causa di un errore di codifica, piuttosto che una limitazione di 'asyncio'. – dano