Domanda

Esiste una penalità per le prestazioni di runtime quando si usano interfacce (classi base astratte) in C ++?

È stato utile?

Soluzione

Risposta breve: No.

Risposta lunga: Non è la classe base o il numero di antenati che una classe ha nella sua gerarchia che influenza la sua velocità. L'unica cosa è il costo di una chiamata di metodo.

Una chiamata di metodo non virtuale ha un costo (ma può essere integrato)
Una chiamata al metodo virtuale ha un costo leggermente più elevato in quanto è necessario cercare il metodo da chiamare prima di chiamarlo (ma questa è una semplice tabella cercare non una ricerca). Poiché tutti i metodi su un'interfaccia sono virtuali per definizione, questo costo è presente.

A meno che tu non stia scrivendo un'applicazione sensibile alla velocità eccessiva, questo non dovrebbe essere un problema. La chiarezza extra che riceverai dall'uso di un'interfaccia di solito compensa qualsiasi diminuzione della velocità percepita.

Altri suggerimenti

Le funzioni chiamate utilizzando l'invio virtuale non sono in linea

Esiste un tipo di penalità per le funzioni virtuali di cui è facile dimenticare: le chiamate virtuali non sono integrate in una situazione (comune) in cui il tipo di oggetto non è noto al momento della compilazione. Se la tua funzione è piccola e adatta per essere incorporata, questa penalità può essere molto significativa, poiché non stai solo aggiungendo un overhead di chiamata, ma il compilatore è anche limitato nel modo in cui può ottimizzare la funzione di chiamata (deve presumere che la funzione virtuale possa hanno modificato alcuni registri o posizioni di memoria, non è in grado di propagare valori costanti tra il chiamante e il chiamante).

Il costo delle chiamate virtuali dipende dalla piattaforma

Per quanto riguarda la penalità ambientale di chiamata rispetto a una normale chiamata di funzione, la risposta dipende dalla piattaforma di destinazione. Se stai prendendo di mira un PC con CPU x86 / x64, la penalità per aver chiamato una funzione virtuale è molto piccola, poiché la moderna CPU x86 / x64 può eseguire la previsione del ramo su chiamate indirette. Tuttavia, se si sta prendendo di mira un PowerPC o un'altra piattaforma RISC, la penalità di chiamata virtuale potrebbe essere piuttosto significativa, poiché le chiamate indirette non sono mai previste su alcune piattaforme (Cfr. PC / Xbox 360 Migliori pratiche di sviluppo multipiattaforma ).

C'è una piccola penalità per chiamata di funzione virtuale rispetto a una chiamata normale. È improbabile che tu osservi una differenza a meno che tu non stia facendo centinaia di migliaia di chiamate al secondo e spesso vale comunque la pena pagare il prezzo per maggiore chiarezza del codice.

Quando chiamate una funzione virtuale (diciamo attraverso un'interfaccia) il programma deve cercare una funzione in una tabella per vedere quale funzione chiamare per quell'oggetto. Ciò comporta una piccola penalità rispetto a una chiamata diretta alla funzione.

Inoltre, quando si utilizza una funzione virtuale, il compilatore non può incorporare la chiamata di funzione. Pertanto potrebbe esserci una penalità nell'uso di una funzione virtuale per alcune piccole funzioni. Questa è generalmente la più grande performance "hit". probabilmente vedrai. Questo è davvero solo un problema se la funzione è piccola e chiamata molte volte, diciamo all'interno di un ciclo.

Un'altra alternativa applicabile in alcuni casi è il tempo di compilazione polimorfismo con modelli. È utile, ad esempio, quando vuoi fare una scelta di implementazione all'inizio del programma e quindi usarlo per la durata dell'esecuzione. Un esempio con polimorfismo di runtime

class AbstractAlgo
{
    virtual int func();
};

class Algo1 : public AbstractAlgo
{
    virtual int func();
};

class Algo2 : public AbstractAlgo
{
    virtual int func();
};

void compute(AbstractAlgo* algo)
{
      // Use algo many times, paying virtual function cost each time

}   

int main()
{
    int which;
     AbstractAlgo* algo;

    // read which from config file
    if (which == 1)
       algo = new Algo1();
    else
       algo = new Algo2();
    compute(algo);
}

Lo stesso usando il polimorfismo del tempo di compilazione

class Algo1
{
    int func();
};

class Algo2
{
    int func();
};


template<class ALGO>  void compute()
{
    ALGO algo;
      // Use algo many times.  No virtual function cost, and func() may be inlined.
}   

int main()
{
    int which;
    // read which from config file
    if (which == 1)
       compute<Algo1>();
    else
       compute<Algo2>();
}

Non penso che il confronto dei costi sia tra una chiamata di funzione virtuale e una chiamata di funzione diretta. Se stai pensando di utilizzare una classe base astratta (interfaccia), allora hai una situazione in cui vuoi eseguire una delle diverse azioni in base al tipo dinamico di un oggetto. Devi fare quella scelta in qualche modo. Un'opzione è utilizzare le funzioni virtuali. Un altro è un interruttore sul tipo di oggetto, tramite RTTI (potenzialmente costoso) o aggiungendo un metodo type () alla classe base (aumentando potenzialmente l'uso della memoria di ciascun oggetto). Quindi il costo della chiamata alla funzione virtuale dovrebbe essere confrontato con il costo dell'alternativa, non con il costo di non fare nulla.

La maggior parte delle persone nota la penalità di runtime, e giustamente.

Tuttavia, nella mia esperienza di lavoro su grandi progetti, i vantaggi di interfacce chiare e incapsulamento adeguato compensano rapidamente il guadagno di velocità. Il codice modulare può essere scambiato per un'implementazione migliorata, quindi il risultato netto è un grande guadagno.

Il tuo chilometraggio può variare e dipende chiaramente dall'applicazione che stai sviluppando.

Una cosa da notare è che il costo delle chiamate alle funzioni virtuali può variare da una piattaforma all'altra. Sulle console potrebbero essere più evidenti, poiché di solito vtable call significa una cache cache e può rovinare la previsione del ramo.

Notare che l'ereditarietà multipla gonfia l'istanza dell'oggetto con più puntatori vtable. Con G ++ su x86, se la tua classe ha un metodo virtuale e nessuna classe base, hai un puntatore a vtable. Se hai una classe base con metodi virtuali, hai ancora un puntatore a vtable. Se hai due classi base con metodi virtuali, hai due puntatori vtable su ciascuna istanza .

Pertanto, con l'ereditarietà multipla (che è l'implementazione delle interfacce in C ++), si pagano le classi di base per la dimensione del puntatore nella dimensione dell'istanza dell'oggetto. L'aumento del footprint di memoria può avere implicazioni indirette sulle prestazioni.

L'uso di classi di base astratte in C ++ generalmente impone l'uso di una tabella di funzioni virtuali, tutte le chiamate dell'interfaccia verranno cercate attraverso quella tabella. Il costo è piccolo rispetto a una chiamata di funzione non elaborata, quindi assicurati di dover andare più veloce di quello prima di preoccuparti.

L'unica principale differenza che conosco è che, dal momento che non stai usando una classe concreta, inline è (molto?) più difficile da fare.

L'unica cosa a cui riesco a pensare è che i metodi virtuali sono un po 'più lenti da chiamare rispetto ai metodi non virtuali, perché la chiamata deve passare attraverso tabella dei metodi virtuali .

Tuttavia, questa è una cattiva ragione per rovinare il tuo design. Se hai bisogno di maggiori prestazioni, usa un server più veloce.

Come per qualsiasi classe che contiene una funzione virtuale, viene usata una vtable. Ovviamente, invocare un metodo attraverso un meccanismo di dispacciamento come una vtable è più lento di una chiamata diretta, ma nella maggior parte dei casi puoi conviverci.

Sì, ma nulla di degno di nota per la mia conoscenza. L'hit di prestazioni è dovuto alla "direzione indiretta" presente in ciascuna chiamata del metodo.

Tuttavia, dipende davvero dal compilatore che stai utilizzando poiché alcuni compilatori non sono in grado di incorporare le chiamate al metodo all'interno delle classi che ereditano dalla classe base astratta.

Se vuoi essere sicuro devi eseguire i tuoi test.

Sì, c'è una penalità. Qualcosa che può migliorare le prestazioni sulla tua piattaforma è usare una classe non astratta senza funzioni virtuali. Quindi utilizzare un puntatore alla funzione membro per la funzione non virtuale.

So che è un punto di vista insolito, ma anche menzionare questo problema mi fa sospettare che stai riflettendo troppo sulla struttura della classe. Ho visto molti sistemi che avevano troppi "livelli di astrazione" e che da soli li rendevano inclini a gravi problemi di prestazioni, non a causa del costo delle chiamate di metodo, ma a causa della tendenza a effettuare chiamate non necessarie. Se questo accade su più livelli, è un killer. dai un'occhiata

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