2010-06-29 20 views
2

J'ai mis en place un petit test (peut-être très peu scientifique) pour déterminer le surdébit des fonctions virtuelles dans un héritage simple à un niveau et les résultats obtenus étaient, bien, exactement les mêmes class polymorphiquement ou lors de l'accès directement. Ce qui était un peu surprenant était l'ordre de grandeur du temps de calcul qui est introduit quand une fonction est déclarée virtuelle (voir les résultats ci-dessous).Test de surcharge des fonctions virtuelles

Y a-t-il tellement de surcharge lors de la déclaration des fonctions membres en tant que telles, et pourquoi est-elle toujours présente même lors de l'accès direct à la classe dérivée?

Le code est le suivant:

class base 
{ 
public: 
    virtual ~base() {} 
    virtual uint func(uint i) = 0; 
}; 

class derived : public base 
{ 
public: 
    ~derived() {} 
    uint func(uint i) { return i * 2; } 
}; 

uint j = 0; 
ulong k = 0; 
double l = 0; 
ushort numIters = 10; 
base* mybase = new derived; // or derived* myderived = ... 

for(ushort i = 0; i < numIters; i++) 
{ 
    clock_t start2, finish2; 
    start2 = clock(); 

    for (uint j = 0; j < 100000000; ++j) 
     k += mybase->func(j); 

    finish2 = clock(); 
    l += (double) (finish2 - start2); 
    std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl; 

} 

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl; 
std::cout << "Average duration: " << l/numIters << " ms." << std::endl; 

Résultats:

base* mybase = new derived; donne une moyenne de ~ 338 ms.

derived* myderived = new derived; donne une moyenne de ~ 338 ms. L'élimination de l'héritage et la suppression des fonctions virtuelles donne une moyenne de ~ 38 ms.

C'est presque 10 fois moins! Donc, fondamentalement, si une fonction est déclarée virtuelle, la surcharge sera toujours identique, même si je ne l'utilise pas de façon polymorphique?

Merci.

+0

Il semble que vous calculiez le coût de l'héritage + fonctions virtuelles ensemble. Vous devez tester l'instanciation d'une classe dérivée sans aucune fonction virtuelle, ainsi que la classe de base. –

+0

Pourquoi utiliser 'new'? Il serait plus simple d'instancier l'objet sur la pile ... –

+0

Cela ne ferait absolument aucune différence dans ce cas. Même surcharge en cas d'accès via un pointeur. –

Répondre

6

L'accès "directement" fait le même travail que l'accès "indirectement".

Lorsque vous appelez la fonction sur myderived, le pointeur stocké là peut pointer vers un objet de classe dérivée de derived. Le compilateur ne peut pas supposer qu'il s'agit vraiment d'un objet derived, il peut s'agir d'un objet d'une autre classe dérivée qui remplace la fonction virtuelle, il doit donc y avoir une distribution de fonction virtuelle comme dans le cas mybase. Dans les deux cas, la fonction est recherchée dans la table de fonctions virtuelle avant d'être appelée.

Pour appeler la fonction non-polymorphically, ne pas utiliser un pointeur:

derived myderived; 
myderived.func(1); 

Lorsque vous supprimez les fonctions virtuelles, le compilateur peut inline l'appel de fonction afin que vous finissez essentiellement avec une simple boucle :

for (uint j = 0; j < 100000000; ++j) 
    k += i * 2; 

Cela est beaucoup plus rapide puisque vous enregistrez les frais généraux de 100.000.000 appels de fonction et le compilateur peut même être en mesure d'optimiser la boucle plus d'une manière, il ne serait pas s'il y avait un appel de fonction en elle. Notez également que la différence entre la version inline et l'appel de fonction virtuelle serait beaucoup moindre si la fonction effectuait un travail réel. Dans cet exemple, le corps de la fonction ne prend presque pas de temps, de sorte que les coûts d'appel de la fonction l'emportent sur les coûts d'exécution du corps.

+0

Je vois merci. Ce que vous avez dit est parfaitement logique. D'accord sur votre point sur la fonction ayant trop peu de travail. La proportion est évidemment faussée ici, mais il est bon de connaître la surcharge de l'appel de fonction. Donc, fondamentalement, à la fin de la journée, une fois que vous déclarez une fonction virtuelle et l'utilisez à travers un pointeur, vous avez payé presque sinon tout le prix de l'utilisation des fonctions virtuelles. –

+0

s/le compilateur ne peut pas supposer que c'est vraiment un dérivé/le compilateur ne suppose pas qu'il est vraiment dérivé /. C'est possible, puisqu'il n'y a pas de classes plus dérivées (note: ne nécessite pas d'optimisation du programme entier) – MSalters

+0

J'ai essayé de supprimer le pointeur, et c'était plus lent qu'avec. –

2

Les fonctions virtuelles ne coûtent pratiquement rien. La plupart des problèmes de performance réels sont causés par des arborescences d'appels sans fin qui font des choses que vous ne devineriez jamais être un problème. La façon dont je les trouve est de mettre l'application plusieurs fois en pause sous le débogueur et d'examiner l'état, y compris la pile des appels. Here's an example d'utiliser cette méthode pour obtenir une accélération de 43x.

+0

Merci Mike c'est une méthode formidable. –