paint-brush
Async vs Sync Benchmark (.NET): la differenza tra metodi asincroni e sincronidi@artemmikulich
1,841 letture
1,841 letture

Async vs Sync Benchmark (.NET): la differenza tra metodi asincroni e sincroni

di Artem Mikulich5m2024/09/21
Read on Terminal Reader

Troppo lungo; Leggere

Questo articolo mostra la differenza tra metodi asincroni e sincroni in pratica. Due istanze di locust indipendenti sono in esecuzione su due macchine. Il numero di utenti cresce in modo uniforme fino al numero di destinazione (*Numero di utenti*). La velocità di crescita è controllata da un parametro *Frequenza di spawn* (numero di utenti univoci che si uniscono al secondo)
featured image - Async vs Sync Benchmark (.NET): la differenza tra metodi asincroni e sincroni
Artem Mikulich HackerNoon profile picture

Una delle mie domande preferite nei colloqui è "Cosa ti dicono parole come async e await ?" perché apre l'opportunità di avere una discussione interessante con un intervistato... O non lo fa perché galleggiano su questo argomento. A mio parere, è drasticamente importante capire perché utilizziamo questa tecnica.


Ho la sensazione che molti sviluppatori preferiscano affidarsi all'affermazione "è la migliore pratica" e utilizzare ciecamente metodi asincroni.


Questo articolo illustra la differenza pratica tra metodi asincroni e sincroni.

Utensili

  • Applicazione Web API .NET (target di test)


  • 2 database SQL di Azure


  • 2 Azure App Service su Windows (ospita l'applicazione)


  • Azure App Insights (per raccogliere metriche)


  • framework locust (per simulare il carico utente).

Configurazione

Schema dell'esperimento

Eseguirò un benchmark nel modo seguente. Due istanze di locust indipendenti sono in esecuzione su due macchine. Le istanze di locust simulano un utente che esegue le seguenti operazioni:

  • L'utente dell'host locust 1 raggiunge l'endpoint sincrono dell'App Service 1, riceve la risposta e rimane inattivo per 0,5-1 secondi (il ritardo esatto è casuale). Si ripete fino alla fine dell'esperimento.


  • L'utente dell'host locust 2 si comporta esattamente nello stesso modo, con una sola differenza: raggiunge l'endpoint asincrono di App Service 2.


Sotto il cofano, ogni App Service si connette al proprio database ed esegue una query SELECT che impiega cinque secondi e restituisce alcune righe di dati. Vedere il codice del controller di seguito per i riferimenti. Userò Dapper per effettuare una chiamata al database. Vorrei attirare la vostra attenzione sul fatto che anche l'endpoint asincrono chiama il database in modo asincrono ( QueryAsync<T> ).


Codice dei servizi app


Vale la pena aggiungere che distribuisco lo stesso codice in entrambi i servizi dell'app.


Durante il test, il numero di utenti cresce in modo uniforme fino al numero target ( Numero di utenti ). La velocità di crescita è controllata da un parametro Spawn Rate (numero di utenti unici che si uniscono al secondo): più alto è il numero, più velocemente vengono aggiunti gli utenti. Lo spawn rate è impostato su 10 utenti/s per tutti gli esperimenti.


Tutti gli esperimenti sono limitati a 15 minuti.


I dettagli sulla configurazione della macchina sono reperibili nella sezione Dettagli tecnici dell'articolo.

Metrica

  • richieste al minuto : mostra il numero di richieste effettivamente elaborate dall'applicazione e che hanno restituito un codice di stato.
  • conteggio thread : mostra il numero di thread consumati dal servizio app.
  • tempo di risposta mediano, ms


Le linee rosse si riferiscono rispettivamente all'endpoint asincrono, mentre le linee blu all'endpoint sincrono.


Questo è tutto per quanto riguarda la teoria. Cominciamo.

Esperimento n. 1

  • numero di utenti : 75 (per servizio)


Possiamo osservare che entrambi gli endpoint hanno prestazioni simili: gestiscono circa 750 richieste al minuto con un tempo di risposta medio di 5200 ms.


Esperimento n. 1. Richieste al minuto


Il grafico più affascinante di questo esperimento è un trend di thread. Puoi vedere numeri significativamente più alti per l'endpoint sincrono (un grafico blu) — più di 100 thread!


Esperimento n. 1. Numero di fili


Ciò è previsto, tuttavia, e corrisponde alla teoria: quando arriva una richiesta e l'applicazione effettua una chiamata al database, il thread viene bloccato perché deve attendere il completamento di un roundtrip. Pertanto, quando arriva un'altra richiesta, l'applicazione deve produrre un nuovo thread per gestirla.


Il grafico rosso, il conteggio dei thread dell'endpoint asincrono, dimostra un comportamento diverso. Quando arriva una richiesta e l'applicazione effettua una chiamata al database, il thread torna a un pool di thread invece di essere bloccato. Pertanto, quando arriva un'altra richiesta, questo thread libero viene riutilizzato. Nonostante le richieste in arrivo crescano, l'applicazione non richiede nuovi thread, quindi il loro conteggio rimane lo stesso.


Vale la pena menzionare la terza metrica: il tempo di risposta mediano . Entrambi gli endpoint hanno mostrato lo stesso risultato: 5200 ms. Quindi, non c'è differenza in termini di prestazioni.


Esperimento n. 1. Riepilogo


Adesso è il momento di tirare su la posta.

Esperimento n. 2

  • numero di utenti : 150


Abbiamo raddoppiato il carico. L'endpoint asincrono gestisce questa attività con successo: la sua richiesta al minuto si aggira intorno a 1500. Il fratello sincrono ha infine raggiunto un numero comparabile di 1410. Ma se guardate il grafico qui sotto, vedrete che ci sono voluti 10 minuti!


Il motivo è che l'endpoint sincrono reagisce all'arrivo di un nuovo utente creando un altro thread, ma gli utenti vengono aggiunti al sistema (solo per ricordarti che lo Spawn Rate è di 10 utenti/s) più velocemente di quanto il server web possa adattarsi. Ecco perché ha messo in coda così tante richieste all'inizio.


Esperimento n. 2. Richieste al minuto


Non sorprende che la metrica del conteggio dei thread sia ancora intorno a 34 per l'endpoint asincrono, mentre è aumentata da 102 a 155 per quello sincrono. Il tempo di risposta mediano si è degradato in modo simile alla velocità di richiesta al minuto : il tempo di risposta sincrono era molto più alto all'inizio dell'esperimento. Se avessi mantenuto il test per 24 ore, i numeri mediani sarebbero diventati pari.


Esperimento n. 2. Riepilogo


Esperimento n. 3

  • numero di utenti : 200


Il terzo esperimento ha lo scopo di dimostrare le tendenze emerse durante il secondo: possiamo osservare un ulteriore degrado dell'endpoint sincrono.


Esperimento n. 3. Riepilogo


Conclusione

Utilizzare operazioni asincrone anziché sincrone non migliora direttamente le prestazioni o l'esperienza utente. Innanzitutto, migliora la stabilità e la prevedibilità sotto pressione. In altre parole, aumenta la soglia di carico in modo che il sistema possa elaborare di più prima che si degradi.

Appendice n. 1. Dettagli tecnici

  • Azure App Service: B1, 100 ACU , 1,75 Gb di memoria, equivalente all'elaborazione della serie A.
  • Database SQL di Azure: Standard S4: 200 DTU, 500 Mb di spazio di archiviazione.
  • Impostazioni di connessione SQL: dimensione massima del pool=200.

Appendice n. 2. Note

Per ottenere un risultato di test più pulito, avrei dovuto eseguire i test da 2 VM situate nella stessa rete in cui si trovano i servizi app di destinazione.


Tuttavia, ho dato per scontato che un ritardo di rete avrebbe avuto un impatto su entrambe le app in modo più o meno simile. Pertanto, non può compromettere l'obiettivo principale, ovvero confrontare il comportamento dei metodi asincroni e sincroni.

Appendice n. 3. Esperimento bonus

Cosa ho modificato per forzare l'endpoint sincrono a comportarsi quasi come asincrono e tracciare il grafico sottostante (le condizioni dell'esperimento sono le stesse del terzo: 200 utenti)?


Esperimento bonus. Richieste al minuto