2015-03-14 21 views
8

Come parte di un progetto di sistema, è necessario implementare un modello di fabbrica. In combinazione con il pattern Factory, stiamo anche utilizzando CRTP, per fornire un set di base di funzionalità che può essere personalizzato dalle classi derivate.C++ Bassa latenza Design: Funzione Invio v/s CRTP per implementazione di fabbrica

codice di esempio riportato di seguito:

class FactoryInterface{ 
    public: 
    virtual void doX() = 0; 
}; 

//force all derived classes to implement custom_X_impl 
template< typename Derived, typename Base = FactoryInterface> 
class CRTP : public Base 
{ 
    public: 
    void doX(){ 
     // do common processing..... then 
     static_cast<Derived*>(this)->custom_X_impl(); 
    } 
}; 

class Derived: public CRTP<Derived> 
{ 
    public: 
     void custom_X_impl(){ 
     //do custom stuff 
     } 
}; 

Anche se questo disegno è contorto, lo fa un forniscono alcuni benefici. Tutte le chiamate successive alla chiamata della funzione virtuale iniziale possono essere sottolineate. Anche la chiamata custom_X_impl della classe derivata viene eseguita in modo efficiente.

Ho scritto un programma di confronto per confrontare il comportamento di un'implementazione simile (ciclo stretto, chiamate ripetute) utilizzando i puntatori di funzione e le funzioni virtuali. Questo progetto ha trionfato per gcc/4.8 con O2 e O3.

Un guru di C++ mi ha detto ieri, che qualsiasi chiamata di funzione virtuale in un programma di esecuzione di grandi dimensioni può richiedere un tempo variabile, considerando i problemi di cache e posso ottenere una prestazione potenzialmente migliore utilizzando le ricerche di tabelle di stile di stile C e hotlist di gcc di funzioni. Tuttavia, vedo ancora il costo 2x nel mio programma di esempio menzionato sopra.

Le mie domande sono le seguenti: 1. L'asserzione del guru è vera? Per entrambe le risposte, ci sono collegamenti che posso riferire. 2. Esiste un'implementazione a bassa latenza che posso riferire, ha una classe base che richiama una funzione personalizzata in una classe derivata, usando i puntatori di funzione? 3. Qualche suggerimento su come migliorare il design?

Qualsiasi altro feedback è sempre benvenuto.

risposta

2

Il guru fa riferimento all'attributo hot del compilatore gcc. L'effetto di questo è attribute:

la funzione è ottimizzata in modo più aggressivo e più bersagli viene collocato in una sottosezione particolare della sezione di testo in modo tutte calde funzioni appaiono ravvicinati, migliorando frazione.

Quindi sì, in una base di codice molto grande, la funzione della hotlist può rimanere nella cache pronta per essere eseguita senza ritardo, perché manca la cache di avodis.

Potete perfettamente utilizzare questo attributo per le funzioni di membro:

struct X { 
    void test() __attribute__ ((hot)) {cout <<"hello, world !\n"; } 
}; 

Ma ...

Quando si utilizzano funzioni virtuali il compilatore genera in genere un vtable che è condivisa tra tutti gli oggetti della classe. Questa tabella è una tabella di puntatori alle funzioni. E infatti - il tuo guru ha ragione - nulla garantisce che questo tavolo rimanga nella memoria nascosta.

Ma, se si crea manualmente una tabella "C-style" di puntatori di funzione, il problema è ESATTAMENTE LO STESSO. Mentre la funzione può rimanere nella cache, nulla garantisce che la tabella delle funzioni rimanga anche nella cache.

La principale differenza tra i due approcci è che:

  • nel caso di funzioni virtuali, il compilatore sa che la funzione virtuale è un hot spot, e potrebbe decidere di fare in modo di mantenere il vtable anche nella cache (non so se gcc può farlo o se ci sono piani per farlo).

  • nel caso della tabella del puntatore funzione manuale, il compilatore non dedurrà facilmente che la tabella appartiene a un punto caldo. Quindi questo tentativo di ottimizzazione manuale potrebbe benissimo ritorcersi contro.

La mia opinione: non cercare mai di ottimizzare ciò che un compilatore può fare molto meglio.

Conclusione

Fidati dei tuoi benchmark. E fidati del tuo sistema operativo: se la tua funzione oi tuoi dati sono frequentemente acquisiti, ci sono alte probabilità che un sistema operativo moderno tenga conto di questo nella sua gestione memry virtuale, e qualunque cosa il compilatore genererà.

+0

Grazie Christophe. La tua analisi si lega anche a ciò che vedo nei test delle prestazioni. Vedi qualche potenziale ottimizzazione nel design? – Sid

+0

Penso che sarà difficile ottimizzare ulteriormente! Poiché doX() deve essere virtuale e Derived è definito dal cliente, non trovo altra alternativa con meno indiretta. – Christophe