std::function Aufruf langsam



  • Hallo,

    ich habe gerade etwas mit Lambdas und std::function herumgespielt und habe mich erschrocken, wie langsam das ist. Ich habe zwei Szenarien getestet, ein Mal einen eigenen Wrapper um ein Lambda und ein Mal ein Lambda in ein std::function gesteckt:

    #include <string>
    #include <vector>
    #include <memory>
    #include <utility>
    #include <algorithm>
    #include <functional>
    
    using namespace std;
    
    struct Base
    {
       Base()
       {
       }
    
       virtual ~Base()
       {
       }
    
       virtual double operator()( double value ) const = 0;
    };
    
    template<typename FuncType>
    struct Derived : Base
    {
       FuncType Func_;
    
       Derived( FuncType&& F ) :
          Func_( forward<FuncType>( F ) )
       {
       }
    
       double operator()( double value ) const override
       {
          return Func_( value );
       }
    };
    
    using BasePtr_t = std::shared_ptr<Base>;
    
    template<typename FuncType>
    function<double (double)> make_func( FuncType&& F )
    {
       return F;
    }
    
    /*
    template<typename FuncType>
    BasePtr_t make_func( FuncType&& F )
    {
       return BasePtr_t( new Derived<FuncType>( forward<FuncType>( F ) ) );
    }
    */
    
    void test_function()
    {
       using func = std::function<double ( double )>;
    //   using func = BasePtr_t;
    
       vector<func> vec;
    
       vec.emplace_back( make_func( [] ( double v ) { return v+v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v*v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return 2 * v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v / 2; } ) );
    
       vec.emplace_back( make_func( [] ( double v ) { return v+v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v*v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return 2 * v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v / 2; } ) );
    
       vec.emplace_back( make_func( [] ( double v ) { return v+v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v*v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return 2 * v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v / 2; } ) );
    
       vec.emplace_back( make_func( [] ( double v ) { return v+v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v*v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return 2 * v; } ) );
       vec.emplace_back( make_func( [] ( double v ) { return v / 2; } ) );
    
       // Messungsbeginn
       for( size_t i = 0; i < 1000000; ++i )
       {
          for( const auto& F : vec )
          {
             F( 16 );
            // (*F)( 16 );
          }
       }
       // Messungsende
    }
    

    Der Vektor mit 16 Elementen wird 1M mal durchlaufen, es sind also insgesamt 16M Aufrufe.
    Die STL Lösung mit std::function<double (double>> braucht dafür 1.49s, die selbstgebastelte Lösung per Vererbung nur 0.92s. Bin jetzt iwie enttäuscht, oder hab ich da grob Mist gebaut?



  • Welcher Compiler etc?

    Ich habe folgende Änderungen vorgenommen:
    1. gesamtsumme wird gebildet, die am Ende ausgegeben wird, also statt F( 16 ); ein sum += F( 16 ); (analog in der anderen Variante).
    2. Ich musste 2 weitere Nullen an die Loop hängen, damit das Programm mehr als 0 Sekunden Zeit braucht, d.h. jetzt gehts von 0 bis 100000000.
    3. int main() dazugefügt, die deine test_function aufruft.

    Ergebnis: beide Varianten brauchen danach jeweils 3.3 Sekunden. Sehe keinen Unterschied.
    i7-4790K, g++ 5.4.0 mit -std=c++14 -O3



  • std::function ist natürlich langsam. Bei SO fand man ne Variante, die schneller ist: http://codereview.stackexchange.com/questions/14730/impossibly-fast-delegate-in-c11



  • wob schrieb:

    Welcher Compiler etc?

    Embarcadero RAD Studio 10.1 Update 2 (clang 3.3.1) -std=c++11 -O2
    CPU i5-4690 @3.5GHz



  • Hallo DocShoe,

    ich kann genau das gegenteilige Ergebnis nachvollziehen:

    std::function: 26.371ms
    DocShoe:       50.5239ms
    

    System: MinGW GCC 6.2.0, i7 6800k @ 4.0GHz, -O3 -std=c++14

    Ich mache, so wie du, 10^6 Durchgänge aber ich summiere die Resultate jeweils.

    Grüße



  • Sehr merkwürdig das alles...



  • Wieso? O3. III. Drei.



  • O3 kann ich mal ausprobieren, das lässt sich über die IDE nicht einstellen. Das Release Build wird standardmäßig mit O2 übersetzt. Ich poste morgen das Resultat.



  • Achja...

    Mit Visual Studio "15", Release-Build mit den üblichen Optimierungen sind bei mir beide ziemlich genau gleich schnell. Leicht angepasster Test Code:

    #include <string>
    #include <vector>
    #include <memory>
    #include <utility>
    #include <algorithm>
    #include <functional>
    #include <chrono>
    #include <iostream>
    
    using namespace std;
    
    struct Base
    {
    	Base()
    	{
    	}
    
    	virtual ~Base()
    	{
    	}
    
    	virtual double operator()(double value) const = 0;
    };
    
    template<typename FuncType>
    struct Derived : Base
    {
    	FuncType Func_;
    
    	Derived(FuncType&& F) :
    		Func_(forward<FuncType>(F))
    	{
    	}
    
    	double operator()(double value) const override
    	{
    		return Func_(value);
    	}
    };
    
    #define FOO
    
    #ifdef FOO
    
    using func = std::function<double(double)>;
    
    template<typename FuncType>
    func make_func(FuncType&& F)
    {
    	return F;
    }
    
    #else
    
    using BasePtr_t = std::shared_ptr<Base>;
    using func = BasePtr_t;
    
    template<typename FuncType>
    BasePtr_t make_func( FuncType&& F )
    {
    return BasePtr_t( new Derived<FuncType>( forward<FuncType>( F ) ) );
    }
    
    #endif
    
    int main()
    {
    
    	vector<func> vec;
    
    	vec.emplace_back(make_func([](double v) { return v + v; }));
    	vec.emplace_back(make_func([](double v) { return v*v; }));
    	vec.emplace_back(make_func([](double v) { return 2 * v; }));
    	vec.emplace_back(make_func([](double v) { return v / 2; }));
    
    	vec.emplace_back(make_func([](double v) { return v + v; }));
    	vec.emplace_back(make_func([](double v) { return v*v; }));
    	vec.emplace_back(make_func([](double v) { return 2 * v; }));
    	vec.emplace_back(make_func([](double v) { return v / 2; }));
    
    	vec.emplace_back(make_func([](double v) { return v + v; }));
    	vec.emplace_back(make_func([](double v) { return v*v; }));
    	vec.emplace_back(make_func([](double v) { return 2 * v; }));
    	vec.emplace_back(make_func([](double v) { return v / 2; }));
    
    	vec.emplace_back(make_func([](double v) { return v + v; }));
    	vec.emplace_back(make_func([](double v) { return v*v; }));
    	vec.emplace_back(make_func([](double v) { return 2 * v; }));
    	vec.emplace_back(make_func([](double v) { return v / 2; }));
    
    	auto t0 = std::chrono::high_resolution_clock::now();
    
    	double d = 0;
    
    	for (size_t i = 0; i < 10000000; ++i)
    	{
    		for (const auto& F : vec)
    		{
    #ifdef FOO
    			d += F(i);
    #else
    			d += (*F)(i);
    #endif
    		}
    	}
    
    	auto t1 = std::chrono::high_resolution_clock::now();
    	auto dur = t1 - t0;
    
    	std::cout << d << "\n";
    	std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(dur).count() << "\n";
    }
    

    Was seltsam ist: wenn ich statt d += (*F)(i); dein originales (*F)(16); drinnen lasse, braucht deine Version ca. 3x so lange. Keine Ahnung wieso. DAS ist seltsam 🙂



  • So, hab das noch mal getestet. 10M Aufrufe, 1x nur den Funktionsaufruf, 1x mit Aufsummierung. Release Build mit -O3 😉

    Methode        | Nur Aufruf | Aufsummierung
    ---------------+------------+--------------
    std::function  |     ~490ms |        ~570ms
    Vererbung      |     ~350ms |        ~500ms
    

    Der clang 3.3.1 ist auch schon steinalt, vllt konnte der sowas noch nicht gut optimieren. Nen moderneren Compiler hab ich grad nicht. Den könnte ich aber auch nicht für Produktivcode einsetzen.


Anmelden zum Antworten