Domanda

Ho sviluppato un mini server HTTP in C ++, usando boost :: asio, e ora sto caricando test con più client e non sono riuscito ad avvicinarmi alla saturazione della CPU. Sto testando su un'istanza di Amazon EC2 e sto ottenendo circa il 50% di utilizzo di una CPU, il 20% di un'altra e le restanti due sono inattive (secondo htop).

dettagli:

  • Il server avvia un thread per core
  • Le richieste vengono ricevute, analizzate, elaborate e le risposte vengono scritte
  • Le richieste sono per i dati, che vengono letti dalla memoria (sola lettura per questo test)
  • Sto "caricando" il server utilizzando due macchine, ciascuna con un'applicazione java, 25 thread in esecuzione, l'invio di richieste
  • Sto vedendo circa 230 richieste / secondo di throughput (si tratta di applicazioni richieste, che sono composte da molte richieste HTTP)

Quindi, cosa dovrei guardare per migliorare questo risultato? Dato che la CPU è principalmente inattiva, mi piacerebbe sfruttare quella capacità aggiuntiva per ottenere un throughput più elevato, diciamo 800 richieste / sec o altro.

Idee che ho avuto:

  • Le richieste sono molto piccole e spesso soddisfatte in pochi ms, potrei modificare il client per inviare / comporre richieste più grandi (magari usando il batch)
  • Potrei modificare il server HTTP per utilizzare il modello di progettazione Seleziona, è appropriato qui?
  • Potrei fare un po 'di profilazione per cercare di capire quali sono / sono i colli di bottiglia
È stato utile?

Soluzione

boost :: asio non è facile da usare come si spera - c'è un grande blocco attorno al codice epoll in boost / asio / detail / epoll_reactor.hpp, il che significa che solo un thread può chiamare nel syscall epoll del kernel Al tempo. E per richieste molto piccole questo fa la differenza (nel senso che vedrai solo prestazioni a thread singolo).

Nota che questa è una limitazione del modo in cui boost :: asio usa le strutture del kernel Linux, non necessariamente il kernel Linux stesso. Epoll syscall supporta più thread quando si utilizzano eventi innescati dal margine, ma farlo correttamente (senza eccessivo blocco) può essere piuttosto complicato.

A proposito, ho fatto un po 'di lavoro in quest'area (combinando un loop di eventi epoll con trigger sul fronte multithread con thread / fibre programmati dall'utente) e ho reso disponibile del codice sotto progetto nginetd .

Altri suggerimenti

Poiché stai utilizzando EC2, tutte le scommesse sono disattivate.

Provalo utilizzando l'hardware reale, quindi potresti essere in grado di vedere cosa sta succedendo. Cercare di eseguire test delle prestazioni nelle macchine virtuali è praticamente impossibile.

Non ho ancora capito a cosa serva EC2, se qualcuno lo scopre, per favore fatemelo sapere.

Dai tuoi commenti sull'utilizzo della rete,
Sembra che tu non abbia molto movimento di rete.

3 + 2,5 MiB / sec si trova intorno al campo da baseball 50 Mbps (rispetto alla porta da 1 Gbps).

Direi che stai riscontrando uno dei seguenti due problemi,

  1. Carico di lavoro insufficiente (basso tasso di richieste dai clienti)
    • Blocco nel server (generazione di risposta interferita)

Guardando le note di cmeerw e le cifre di utilizzo della CPU
(inattivo con 50% + 20% + 0% + 0% )
sembra molto probabilmente una limitazione nell'implementazione del tuo server.
In secondo luogo la risposta di cmeerw (+1).

230 richieste / sec sembrano molto basse per tali semplici richieste asincrone. Pertanto, l'utilizzo di più thread è probabilmente un'ottimizzazione prematura: farlo funzionare correttamente e sintonizzato in un singolo thread e vedere se ne hai ancora bisogno. Il solo fatto di sbarazzarsi del blocco non necessario può velocizzare le cose.

Questo articolo contiene alcuni dettagli e discussioni sulle strategie di I / O per il server web stile 2003. Qualcuno ha qualcosa di più recente?

ASIO va bene per le attività medio-piccole ma non è molto bravo a sfruttare la potenza del sistema sottostante. Né sono chiamate socket non elaborate, o persino IOCP su Windows, ma se hai esperienza sarai sempre migliore di ASIO. Ad ogni modo c'è un sacco di sovraccarico con tutti questi metodi, solo di più con ASIO.

Per quello che vale. l'utilizzo di chiamate socket non elaborate sul mio HTTP personalizzato può servire 800K richieste dinamiche al secondo con un I7 a 4 core. Serve dalla RAM, che è dove devi essere per quel livello di prestazioni. A questo livello di prestazioni, il driver di rete e il sistema operativo consumano circa il 40% della CPU. Usando ASIO posso ottenere da 50 a 100.000 richieste al secondo, le sue prestazioni sono piuttosto variabili e principalmente legate alla mia app. Il post di @cmeerw spiega principalmente perché.

Un modo per migliorare le prestazioni è implementando un proxy UDP. Intercettando le richieste HTTP e quindi instradandole su UDP al tuo server UDP-HTTP back-end, puoi bypassare un sacco di overhead TCP negli stack del sistema operativo. Puoi anche avere front-end che passano attraverso UDP, il che non dovrebbe essere troppo difficile da fare da solo. Un vantaggio di un proxy HTTP-UDP è che ti consente di utilizzare qualsiasi frontend valido senza modifiche e puoi scambiarli a piacimento senza alcun impatto. Hai solo bisogno di un paio di server in più per implementarlo. Questa modifica sul mio esempio ha ridotto l'utilizzo della CPU del sistema operativo al 10%, il che ha aumentato le mie richieste al secondo a poco più di un milione su quel singolo back-end. E FWIW Dovresti sempre avere una configurazione frontend-backend per qualsiasi sito performante perché i frontend possono memorizzare nella cache i dati senza rallentare il più importante backend delle richieste dinamiche.

Il futuro sembra scrivere il proprio driver che implementa il proprio stack di rete in modo da poter avvicinarsi il più possibile alle richieste e implementare il proprio protocollo lì. Il che probabilmente non è ciò che la maggior parte dei programmatori vuole ascoltare in quanto è più complicato. Nel mio caso sarei in grado di utilizzare il 40% in più di CPU e passare a oltre 1 milione di richieste dinamiche al secondo. Il metodo proxy UDP può avvicinarti alle prestazioni ottimali senza doverlo fare, tuttavia avrai bisogno di più server - anche se se stai facendo così tante richieste al secondo di solito avrai bisogno di più schede di rete e più frontend per gestire la larghezza di banda, quindi avendo un paio di proxy UDP leggeri non sono un grosso problema.

Spero che parte di questo possa esserti utile.

Quante istanze di io_service hai? Boost asio ha un esempio che crea un io_service per CPU e usarli come RoundRobin.

Puoi comunque creare quattro thread e assegnarne uno per CPU, ma ogni thread può eseguire il polling sul proprio io_service.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top