2012-03-20 9 views
7

Stavo scrivendo un framework di registrazione asincrono, in cui ho avuto più thread che scaricano dati. Ho iniziato a giocare su Boost asio perché offriva alcuni modi semplici per applicare la serializzazione e l'ordinamento. Dato che sono un principiante, ho iniziato il mio design con un thread safe (usato e boost:condition_variable) bounded_buffer circolare (che in realtà era vettoriale).Boost ASIO IO_SERVICE Implementazione?

Ho scritto un piccolo benchmark semplice per misurare le prestazioni. Il benchmark è solo un singolo thread che registra un milione di messaggi (spingendolo nel buffer) e il mio thread di lavoro si limita ad afferrare i messaggi dalla coda per accedere al file/console/elenco dei logger. (P.S. L'uso di mutex e C.V era corretto, e i puntatori ai messaggi venivano spostati, quindi da quella prospettiva tutto andava bene/efficiente).

Quando ho cambiato attuazione usando invece boost::asio::io_service e ed avente un singolo filo esecuzione run() le prestazioni realmente migliorata (in realtà in scala molto bene sull'aumento del numero di messaggi viene registrato in contrasto con le prestazioni degradano nel mio modello semplice iniziale)

Ecco alcune domande che desidero chiarire.

  1. Perché migliorare le prestazioni? (Pensavo che l'implementazione interna di boost::asio::io_service abbia una coda di sicurezza dei thread per i gestori, cosa che lo rende molto più efficiente della mia iniziale progettazione di code di sicurezza con thread semplici). Si prega di prendere nota che il mio design è stato ben recensito e non ha avuto difetti in quanto tale (il codice dello scheletro era basato su esempi dimostrati), qualcuno potrebbe fare più luce sui dettagli interni di come io_service implementa questo. La seconda osservazione interessante era che su thread in aumento, le mie prestazioni di implementazione iniziale miglioravano ma al costo di perdere la serializzazione/l'ordine, ma le prestazioni si riducevano (leggermente) con boost :: asio (penso che sia perché i miei gestori stavano facendo un compito molto semplicistico e il sovraccarico di commutazione del contesto era degradante, proverò a mettere un compito più complesso e postare le mie osservazioni più tardi).

  2. mi piacerebbe davvero sapere se boost::asio è solo significato per le operazioni di I/O e di rete o è il mio utilizzo di usarlo per fare compito concomitante (parallelo) attraverso un pool di thread è un buon approccio progettuale. È l'oggetto io_service destinato a essere utilizzato per gli oggetti di I/O (come scritto nella documentazione), ma ho trovato un modo davvero interessante di aiutarmi a risolvere attività concorrenti (non solo i/o relative alle reti) in modo serializzato (a volte forzando ordinare usando filamenti). Sono nuovo per aumentare, e davvero curioso perché il modello di base non ha funzionato/scala così come quando ho usato boost asio.

Risultati: (in entrambi i appena avuto 1 thread di lavoro)

  • 1000 compito: 10 micro secondi/compito in entrambi i casi
  • 10000 compito: 80 micro secondi (tampone delimitata), 10 micro secondi in spinta asio
  • 100000 compito: 250 micro secondi (tampone bounde), 10 micro secondi in spinta asio

sarebbe interessante k ora come boost risolve il problema di thread thread in io_service thread safe queue per i gestori (ho sempre pensato che a un certo livello di implementazione debbano usare anche lock e c.v).

+0

Ho usato 'boost :: asio' per gestire altri tipi di attività asincrone (non solo IO di rete) con successo. – Chad

+0

So che questa è una vecchia domanda ma potrebbe essere che (su Windows), ASIO usi le porte di completamento dell'IO e questo potrebbe comportare meno chiamate di sistema in generale. – Pete

risposta

4

temo non posso fare molto con (1), ma rispetto alle altre due domande:

(2) ho scoperto che ci sono alcune spese generali nell'architettura boost::asio che non sono -deterministico, ovvero che i ritardi tra i dati in arrivo (o che vengono inviati a un oggetto di servizio IO) possono variare da una risposta praticamente istantanea fino all'ordine di centinaia di millisecondi. Ho tentato di quantificarlo come parte di un altro problema che stavo cercando di risolvere in relazione ai dati RS232 di registrazione e registrazione cronologica, ma non ho ottenuto risultati conclusivi o modi per stabilizzare la latenza. Non mi sorprenderebbe affatto scoprire che esistevano problemi simili con il componente di cambio di contesto.

(3) Per quanto riguarda l'utilizzo di boost::asio per attività diverse dall'I/O asincrono, è ora il mio strumento standard per la maggior parte delle operazioni asincrone. Uso sempre i timer boost::asio per i processi asincroni e per generare timeout per altre attività. La possibilità di aggiungere più thread di lavoro nel pool significa che è possibile ridimensionare la soluzione per altre attività asincrone ad alto carico. Il mio semplice e classe preferita ho scritto nel corso dell'ultimo anno è un piccolo po 'di classe thread di lavoro per boost::asio servizi IO (scuse se ci sono errori di battitura, si tratta di una trascrizione dalla memoria, piuttosto che una pasta taglio &):

class AsioWorker 
{ 
public: 
    AsioWorker(boost::asio::io_service * service): 
    m_ioService(service), m_terminate(false), m_serviceThread(NULL) 
    { 
    m_serviceThread = new boost::thread(boost::bind(&AsioWorker::Run, this)) 
    } 
    void Run(void) 
    { 
    while(!m_terminate) 
     m_ioService->poll_one(); 
     mySleep(5); // My own macro for cross-platform millisecond sleep 
    } 
    ~AsioWorker(void) 
    { 
    m_terminate = true; 
    m_serviceThread->join(); 
    } 
private: 
    bool m_terminate; 
    boost::asio::io_service *m_ioService; 
    boost::thread *m_serviceThread; 
} 

Questa classe è un piccolo grande giocattolo, basta aggiungere new a seconda delle esigenze, e delete alcuni quando hai finito con loro. Inserisci un std::vector<AsioWorker*> m_workerPool in una classe di dispositivo che utilizza boost::asio e puoi avvolgere ulteriormente la roba di gestione del pool di thread. Sono sempre stato tentato di scrivere un gestore automatico di pool intelligente basato sui tempi per far crescere il pool di thread in modo appropriato, ma non ho ancora avuto un progetto dove fosse necessario.

Rispetto alla soddisfazione della vostra curiosità per la sicurezza del thread, è possibile scavare nel coraggio di boost per scoprire esattamente come fanno quello che stanno facendo. Personalmente ho sempre preso la maggior parte della roba di potenziamento al valore nominale e ho assunto dall'esperienza passata che è abbastanza ben ottimizzata sotto il cofano.

0

Ho anche trovato boost::asio come infrastruttura eccellente per un motore di elaborazione multi-core generale. Ho misurato le sue prestazioni su un compito a grana fine con molte sincronizzazioni e ho scoperto che ha sovraperformato un'implementazione "classica" che ho scritto usando i thread e le variabili di condizione C++ 11.

Ha inoltre superato le prestazioni di TBB, ma non di altrettanto. Ho scavato nel loro codice per cercare di trovare il "segreto". L'unico pensiero che posso vedere è che la loro coda è una classica lista collegata, non un contenitore stl.

Per tutto questo, non sono sicuro di quanto bene lo asio si ridimensiona su un'architettura molto filettata come la Xeon Phi. Le due cose che sembrano mancare sono:

  1. una coda di priorità e
  2. un'opera rubare coda.

Sospetto che l'aggiunta di queste funzionalità ridurrebbe al livello di prestazioni TBB.

Problemi correlati