2 votes

L'appel de fonction virtuelle est-il plus lent que le dynamic_cast ?

Je sais que dynamic_cast a un coût important, mais lorsque j'essaie les codes suivants, j'obtiens une valeur plus grande presque à chaque fois à partir de la boucle d'appel de la fonction virtuelle. Est-ce que je me suis trompé jusqu'à présent ?

EDIT : Le problème était que mon compilateur était en mode debug. Lorsque je suis passé en mode release, la boucle d'appel de fonction virtuelle s'exécute 5 à 7 fois plus vite que la boucle dynamic_cast.

struct A {
    virtual void foo() {}
};

struct B : public A {
    virtual void foo() override {}
};

struct C : public B {
    virtual void foo() override {}
};

int main()
{
    vector<A *> vec;
    for (int i = 0; i < 100000; ++i)
        if (i % 2)
            vec.push_back(new C());
        else
            vec.push_back(new B());

    clock_t begin = clock();
    for (auto iter : vec)
        if (dynamic_cast<C*>(iter))
            ;
    clock_t end = clock();
    cout << (static_cast<double>(end) - begin) / CLOCKS_PER_SEC << endl;

    begin = clock();
    for (auto iter : vec)
        iter->foo();
    end = clock();

    cout << (static_cast<double>(end) - begin) / CLOCKS_PER_SEC << endl;

    return 0;
}

1voto

R Sahu Points 24027

Puisque vous ne faites rien avec le résultat de l'action dynamic_cast dans les lignes

for (auto iter : vec)
    if (dynamic_cast<C*>(iter))
        ;

le compilateur pourrait optimiser la plupart de ce code, sinon tout.

Si vous faites quelque chose d'utile avec le résultat de l'opération dynamic_cast vous verrez peut-être une différence. Vous pourriez essayer :

for (auto iter : vec)
{
    if (C* cptr = dynamic_cast<C*>(iter))
    {
        cptr->foo();
    }
    if (B* bptr = dynamic_cast<B*>(iter))
    {
        bptr->foo();
    }
}

Cela fera très probablement la différence.

Voir http://ideone.com/BvqoqU pour un échantillon.

0voto

DOUGLAS O. MOEN Points 837

Est-ce que j'ai eu tort jusqu'à maintenant ?

Nous ne pouvons probablement pas le dire à partir de votre code. L'optimiseur est intelligent, et il est parfois difficile de le "vaincre" ou de le "tromper".

Dans ce qui suit, j'utilise 'assert()' pour essayer de contrôler l'enthousiasme de l'optimiseur. Notez également que 'time(0)' est une fonction rapide sur Ubuntu 15.10. Je pense que le compilateur ne sait pas encore ce que fera la combinaison, et donc ne la supprimera pas, fournissant une mesure plus fiable/répétable.

Je pense que je préfère ces résultats, et peut-être qu'ils indiquent que la distribution dynamique es plus lent que l'invocation de fonctions virtuelles.

Environnement :

on an older Dell, using Ubuntu 15.10, 64 bit, and -O3 

~$ g++-5 --version
g++-5 (Ubuntu 5.2.1-23ubuntu1~15.10) 5.2.1 20151028

Résultats (casting dynamique suivi d'une fonction virtuelle) :

  void T523_t::testStruct()

  0.443445

  0.184873

  void T523_t::testClass()

   252,495 us

   184,961 us

  FINI   2914399 us

Code :

#include <chrono>
// 'compressed' chrono access --------------vvvvvvv
typedef std::chrono::high_resolution_clock  HRClk_t; // std-chrono-hi-res-clk
typedef HRClk_t::time_point                 Time_t;  // std-chrono-hi-res-clk-time-point
typedef std::chrono::milliseconds           MS_t;    // std-chrono-milliseconds
typedef std::chrono::microseconds           US_t;    // std-chrono-microseconds
typedef std::chrono::nanoseconds            NS_t;    // std-chrono-nanoseconds
using   namespace std::chrono_literals;          // support suffixes like 100ms, 2s, 30us
#include <iostream>
#include <iomanip>
#include <vector>
#include <cassert>

// original ////////////////////////////////////////////////////////////////////
struct A {
   virtual ~A() = default; // warning: ‘struct A’ has virtual functions and
                      // accessible non-virtual destructor [-Wnon-virtual-dtor]
   virtual void foo() { assert(time(0)); }
};

struct B : public A {
   virtual void foo() override { assert(time(0)); }
};

struct C : public B {
    virtual void foo() override { assert(time(0)); }
};

// with class ////////////////////////////////////////////////////////////////////////////
// If your C++ code has no class ... why bother?
class A_t {
public:
   virtual ~A_t() = default; // warning: ‘struct A’ has virtual functions and
                      // accessible non-virtual destructor [-Wnon-virtual-dtor]
   virtual void foo() { assert(time(0)); }
};

class B_t : public A_t {
public:
   virtual void foo() override { assert(time(0)); }
};

class C_t : public B_t {
public:
   virtual void foo() override { assert(time(0)); }
};

class T523_t
{
public:

   T523_t() = default;
   ~T523_t() = default;

   int exec()
      {
         testStruct();

         testClass();

         return(0);
      }

private: // methods

   std::string digiComma(std::string s)
      {  //vvvvv--sSize must be signed int of sufficient size
         int32_t sSize = static_cast<int32_t>(s.size());
         if (sSize > 3)
            for (int32_t indx = (sSize - 3); indx > 0; indx -= 3)
               s.insert(static_cast<size_t>(indx), 1, ',');
         return(s);
      }

   void testStruct()
      {
         using std::vector;
         using std::cout; using std::endl;

         std::cout << "\n\n  " << __PRETTY_FUNCTION__ << std::endl;

         vector<A *> vec;
         for (int i = 0; i < 10000000; ++i)
            if (i % 2)
               vec.push_back(new C());
            else
               vec.push_back(new B());

         clock_t begin = clock();
         int i=0;
         for (auto iter : vec)
         {
            if(i % 2) (assert(dynamic_cast<C*>(iter))); // if (dynamic_cast<C*>(iter)) {};
            else      (assert(dynamic_cast<B*>(iter)));
         }

         clock_t end = clock();
         cout << "\n  " << std::setw(8)
              << ((static_cast<double>(end)  -  static_cast<double>(begin))
                  / CLOCKS_PER_SEC) << endl;  //^^^^^^^^^^^^^^^^^^^^^^^^^^
         // warning: conversion to ‘double’ from ‘clock_t {aka long int}’ may alter its value [-Wconversion]

         begin = clock();
         for (auto iter : vec)
            iter->foo();
         end = clock();

         cout << "\n  " << std::setw(8)
              << ((static_cast<double>(end)  -  static_cast<double>(begin))
                  / CLOCKS_PER_SEC) << endl;  //^^^^^^^^^^^^^^^^^^^^^^^^^^
         // warning: conversion to ‘double’ from ‘clock_t {aka long int}’ may alter its value [-Wconversion]
      }

   void testClass()
      {
         std::cout << "\n\n  " << __PRETTY_FUNCTION__ << std::endl;
         std::vector<A_t *> APtrVec;
         for (int i = 0; i < 10000000; ++i)
         {
            if (i % 2)   APtrVec.push_back(new C_t());
            else         APtrVec.push_back(new B_t());
         }

         {
            Time_t start_us = HRClk_t::now();
            int i = 0;
            for (auto Aptr : APtrVec)
            {
               if(i % 2) assert(dynamic_cast<C_t*>(Aptr)); // check for nullptr
               else      assert(dynamic_cast<B_t*>(Aptr)); // check for nullptr
               ++i;
            }
            auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);
            std::cout << "\n  " << std::setw(8)
                      << digiComma(std::to_string(duration_us.count()))
                      << " us" << std::endl;
         }

         {
            Time_t start_us = HRClk_t::now();
            for (auto Aptr : APtrVec) {
               Aptr->foo();
            }
            auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);
            std::cout << "\n  " << std::setw(8)
                      << digiComma(std::to_string(duration_us.count()))
                      << " us" << std::endl;
         }
      }

}; // class T523_t

int main(int argc, char* argv[])
{
   std::cout << "\nargc: " << argc << std::endl;
   for (int i = 0; i < argc; i += 1) std::cout << argv[i] << " ";
   std::cout << std::endl;

   setlocale(LC_ALL, "");
   std::ios::sync_with_stdio(false);
   { time_t t0 = std::time(nullptr); while(t0 == time(nullptr)) { /**/ }; }

   Time_t start_us = HRClk_t::now();

   int retVal = -1;
   {
      T523_t   t523;
      retVal = t523.exec();
   }

   auto  duration_us = std::chrono::duration_cast<US_t>(HRClk_t::now() - start_us);

   std::cout << "\n  FINI   " << (std::to_string(duration_us.count()))
             << " us" << std::endl;
   return(retVal);
}

mise à jour 2017-08-31

Je soupçonne que beaucoup d'entre vous s'opposeront à l'exécution de la distribution dynamique sans utiliser le résultat. Voici une approche possible en remplaçant la boucle for-auto dans la méthode testClass() :

for (auto Aptr : APtrVec)
{
   if(i % 2) { C_t* c = dynamic_cast<C_t*>(Aptr); assert(c); c->foo(); }
   else      { B_t* b = dynamic_cast<B_t*>(Aptr); assert(b); b->foo(); }
   ++i;
}

Avec les résultats

  void T523_t::testStruct()

  0.443445

  0.184873

  void T523_t::testClass()

   322,431 us

   191,285 us

  FINI   4156941 us

terminer la mise à jour


Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X