In che modo più livelli di ereditarietà influiscono sull'overhead delle chiamate alle funzioni virtuali in C ++?

3

Sto considerando l'utilizzo di una gerarchia di classi con più di un singolo livello di ereditarietà, in cui le funzioni dei membri virtuali formano una "catena", ad esempio:

struct Base
{ virtual void foo(); };

struct D1 : Base
{ virtual void foo(); };

struct D2 : D1
{ void foo(); };

Tuttavia mi piacerebbe sapere prima di usare un codice simile nella pratica se ciò causasse un overhead di dispatch dinamico aggiuntivo quando si chiama attraverso un puntatore di classe base. Considera il seguente esempio:

D2 instance;
D1* d1ptr = &instance;
Base* baseptr = &instance;

//normal function call to "D2::foo()", no dynamic dispatch.
instance.foo();
//virtual function call to "D2::foo()", one dynamic dispatch.
d1ptr.foo(); 
//virtual function call to "D2::foo()", does this incur additional
//..overhead compared to "d1ptr.foo()"?
baseptr.foo();
    
posta jms 20.09.2014 - 01:56
fonte

1 risposta

7

Nel tuo esempio, la profondità dell'albero di ereditarietà non influisce sulle prestazioni. La ragione è semplice, ogni istanza ha il suo puntatore a qualche vtable (contenente i puntatori di funzione), e una chiamata di funzione virtuale va solo attraverso quel vtable.

In altre parole, il tuo codice funziona come il seguente codice C:

struct vtable {
   void (*fooptr) (struct D1*);
};

struct D1 {
   const struct vtable*vptr;
};

struct D2 {
   const struct vtable*vptr;
};

E ad es. baseptr->foo() è "trasformato" al tempo di compilazione a baseptr->vptr.fooptr(baseptr) (e ovviamente d1ptr->foo() è "trasformato" in d1ptr->vptr.fooptr(d1ptr) ecc ...)

Quindi il costo di esecuzione è lo stesso: prendi il vtable e chiama indirettamente il puntatore alla funzione all'interno. Il costo rimane lo stesso anche se disponi di una sottoclasse struct D4 di struct D3 sottoclasse di struct D2 sottoclasse di struct D1 . Potresti avere un struct D99 con 99 ereditari e il costo di esecuzione sarebbe rimasto lo stesso.

Su alcuni processori, qualsiasi chiamata indiretta potrebbe essere più lenta di una chiamata diretta , ad es. a causa del predittore di ramo costo

I dati del vtable sono costruiti al momento della compilazione (come dati statici costanti).

Le cose diventano un po 'più complesse con ereditarietà virtuale e / o ereditarietà multipla

BTW, alcuni framework di oggetti C (ad es. GObject di Gtk, o diverse strutture di dati nel kernel di Linux) fornire i loro dati vtable (o di classe). Puntare a una struttura dati contenente puntatori di funzione è abbastanza comune (anche in C). Leggi anche le chiusure e vedrai una relazione con "vtables" (sia le chiusure che gli oggetti si mescolano codice con dati).

Alcuni compilatori C ++ (ad esempio GCC ) possono fornire devirtualization come tecnica di ottimizzazione.

    
risposta data 20.09.2014 - 07:47
fonte

Leggi altre domande sui tag