verschiedene Funktionen benchmarken



  • Hallo zusammen,

    ich bin nach einigen Jahren mal wieder am coden und ich bräuchte etwas Unterstützung oder einen Tipp. Wenn ich bislang die Laufzeit von Funktionen ermitteln wollte, dann habe ich das typischerweise mit std::chrono gemacht:

    	int iter = 1000; // hier z. B. 1000 iterationen für genauere Messungen
    	auto start = high_resolution_clock::now();
    	for (int i = 0; i < iter; i++)
    	{
    	    meine_tolle_Funktion(); // function under test
    	}
    	auto stop = high_resolution_clock::now();
    	auto duration = duration_cast<nanoseconds>(stop - start);
    	double time = (double)duration.count() / (double)(iter * 1000);
    	std::cout << "time: " << std::fixed << std::setprecision(2) << time << " us" << std::endl;
    

    nun ist es so, dass ich viele verschiedene Funktionen mit einer unterschiedlichen Anzahl an Patametern testen möchte. Es ist aber etwas unhandlich diesen Code nun um jeden Funktionsaufruf zu kopieren. Kann man irgendwie so etwas machen?:

    int benchmark(???)
    {
    	// ... Messung starten ....
    	uebergebene_Funktion(..., ... ,...);
    	// ... Messung stoppen ....
    	
    	return ausfuehrungsdauer;
    }
    
    void main()
    {
    	std::cout << "Dauer Funktion A: " << benchmark(meine_tolle_Funktion_A(123, 234, 345)) << std::endl;
    	std::cout << "Dauer Funktion B: " << benchmark(meine_tolle_Funktion_B(ABC, DEF)) << std::endl;
    	std::cout << "Dauer Funktion C: " << benchmark(meine_tolle_Funktion_C(XYZ)) << std::endl;
    }
    

    ....also, dass ich irgendwie die zu messende Funktion an eine Benchmark-Funktion übergebe?

    Ich bin für jeden Tipp dankbar.
    LG, SBond



  • Vielleicht ist das hier was für dich:
    Google Benchmark



  • vielen Dank.
    Sieht interessant auch. Ich werde mir das mal anschauen und mal sehen ob es hilft.

    LG,
    SBond



  • Ja, Google Benchmark ist eine gute Wahl.

    Wenn du selbst einen ganz einfachen Weg gehen willst (hat dann entsprechend Nachteile, die Bibliotheken lösen), dann kannst du dir doch einfach ne total simple Klasse machen:

    struct MeasureTime {
       MeasureTime() { hier clock starten und Zeit speichern }
       void show_elapsed() const { hier duration berechnen und ausgeben }
       ~MeasureTime() { show_elapsed(); }
    };
    
    ...
    
    { MeasureTime mt;
      for (1000 times) run_function_a();
    }
    { MeasureTime mt;
      for (1000 times) run_function_b();
    }
    

    Sowas funktioniert insbesondere dann gut, wenn man länger dauernde Funktionen hat und nicht so genau messen muss und nicht auf Fancy-Vergleichsfeatures angewiesen ist.



  • Dieser Beitrag wurde gelöscht!


  • ja cool 🙂
    via Destruktor kann man das auch machen. Google Benchmark wäre für meine Quick&Dirty tests doch ein kleiner Overhead, den ich vermeiden möchte. Ich hatte dann noch irgendwie eine Überlegung Richtung Funktionspointer, aber nichts konkretes.

    Habe hier noch eine Lösung:

    #define TEST(X)                                                                                    \
    {                                                                                                  \
           int iter = 1000;                                                                            \
           auto start = high_resolution_clock::now();                                                  \
           for (int i = 0; i < iter; i++)                                                              \
           {                                                                                           \
                X;                                                                                     \
           }                                                                                           \
           auto stop = high_resolution_clock::now();                                                   \
           auto duration = duration_cast<nanoseconds>(stop - start);                                   \
           double time = (double)duration.count() / (double)(iter * 1000);                             \
           std::cout << "time: " << std::fixed << std::setprecision(2) << time << " us" << std::endl;  \
    }
    
    void main()
    {
        TEST(meine_tolle_Funktion_A(123, 234, 345));
        TEST(meine_tolle_Funktion_B(ABC, DEF));
        TEST(meine_tolle_Funktion_C(XYZ));
    }
    

    zugegeben, nicht sehr elegant. ...aber es funktioniert xD
    Außerdem noch parametrierbar 🙂

    euch allen nochmals danke.
    habt ein schönes Wochenende



  • Ich kann dir noch eine C++ Lösung ohne Makros anbieten:

    #include <chrono>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <thread>
    
    template<typename Callable, typename ...Params>
    void benchmark( Callable callable, Params&&... p )
    {
       auto const start = std::chrono::high_resolution_clock::now();
       callable( std::forward<Params>( p )... );
       auto const stop = std::chrono::high_resolution_clock::now();
       auto const duration = std::chrono::duration_cast<std::chrono::microseconds>( stop - start );
       std::cout << "time: " << std::fixed << std::setprecision(2) << duration.count() << "µs" << std::endl;
    }
    
    void f()
    {
       std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
    }
    
    void g( unsigned int duration )
    {
       std::this_thread::sleep_for( std::chrono::milliseconds( duration ) );
    }
    
    void h( std::size_t count, std::string const& message )
    {
       for( size_t i = 0; i < count; ++i )
       {
          std::cout << i << ": " << message << "\n";
       }
    }
    
    int main()
    {
       benchmark( f );
       benchmark( g, 20 );
       benchmark( h, 4, "Hello World" );
       return 0;
    }
    


  • Oder leicht angepasst mit lambda syntax

    #include <chrono>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <thread>
    #include <functional>
    
    void benchmark(const std::function<void()>& callable_wrapper)
    {
       auto const start = std::chrono::high_resolution_clock::now();
       callable_wrapper();
       auto const stop = std::chrono::high_resolution_clock::now();
       auto const duration = std::chrono::duration_cast<std::chrono::microseconds>( stop - start );
       std::cout << "time: " << std::fixed << std::setprecision(2) << duration.count() << "µs" << std::endl;
    }
    
    void f()
    {
       std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
    }
    
    void g( unsigned int duration )
    {
       std::this_thread::sleep_for( std::chrono::milliseconds( duration ) );
    }
    
    void h( std::size_t count, std::string const& message )
    {
       for( size_t i = 0; i < count; ++i )
       {
          std::cout << i << ": " << message << "\n";
       }
    }
    
    int main()
    {
       benchmark([]{ f(); });
       benchmark([]{ g(20); });
       benchmark([]{ h(4, "Hello World"); });
       return 0;
    }
    

    Bisschen mehr Schreibarbeit, aber dafür der normale funktionsaufruf sichtbar.

    Oder wahlweise:

    benchmark(std::bind(h, 4, "Hello World"));
    

    was auch etwas expliziter ist und mir deswegen besser gefällt (als die Lösung von DocShoe, persönlich würde ich eher lambda als std::bind nutzen)



  • Jau, gefällt mir auch besser 😉



  • vielen Dank 🙂

    wieder was gelernt 😁



  • @Leon0402 sagte in verschiedene Funktionen benchmarken:

    void benchmark(const std::function<void()>& callable_wrapper)
    

    geht das auch irgendwie so?:

    void benchmark(const std::function<int()>& callable_wrapper)
    

    bekomme dann immer diesen Fehler in visual studio 😕

    Fehler C2664 "void benchmark(const std::function<int (void)> &)" : Konvertierung von Argument 1 von "execute2::<lambda_65e5234ff049377ee7d6eeabbe858357>" in "const std::function<int (void)> &" nicht möglich
    


  • @SBond sagte in verschiedene Funktionen benchmarken:

    geht das auch irgendwie so?:
    void benchmark(const std::function<int()>& callable_wrapper)

    bekomme dann immer diesen Fehler in visual studio

    Das sollte gehen. Aber dann musst du deine Lamdas natürlich ändern (z.B. [](int){}) und den Aufruf von callable_wrapper() logischerweise auch durch callable_wrapper(irgendein_int) ändern.

    std::function hat auch overhead im Gegensatz zur getemplateten Funktion. Falls du micro-Benchmarking machst, könnte das u.U. relevant sein, wenn du eine Loop um den callable_wrapper bauen willst.

    PS: warum eigentlich die std::function als const&? Ich würde die normalerweise per value nehmen, aber kommt wahrscheinlich alles immer darauf an 🙂

    PS2: ACH VERDAMMT! ES IST ZU FRÜH FÜR MICH!
    Ich habe statt void benchmark(const std::function<int()>& callable_wrapper) irgendwie void benchmark(const std::function<void(int)>& callable_wrapper) gelesen. Sorry.

    Aber auch void benchmark(const std::function<int()>& callable_wrapper) geht natürlich, dann musst du in deinem Lambda aber einen int oder irgendwas in-int-konvertierbares returnen. Wie sieht denn dein Lambda aus?



  • vielen Dank! ...und ich kam gestern selbst nach vielen Stunden einfach nicht darauf.

    Hier mal der angepasste code von Leon0402:

    #include <chrono>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <thread>
    #include <functional>
    
    void benchmark(const std::function<int()>& callable_wrapper)
    {
       int retVal = 0;
       auto const start = std::chrono::high_resolution_clock::now();
       retVal  = callable_wrapper();
       auto const stop = std::chrono::high_resolution_clock::now();
       auto const duration = std::chrono::duration_cast<std::chrono::microseconds>( stop - start );
       std::cout << "time: " << std::fixed << std::setprecision(2) << duration.count() << "µs" << "  retVal:" << retVal << std::endl;
    }
    
    void f()
    {
       std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
       return 1;
    }
    
    void g( unsigned int duration )
    {
       std::this_thread::sleep_for( std::chrono::milliseconds( duration ) );
       return 2;
    }
    
    void h( std::size_t count, std::string const& message )
    {
       for( size_t i = 0; i < count; ++i )
       {
          std::cout << i << ": " << message << "\n";
       }
       return 3;
    } 
    
    int main()
    {
       benchmark([]{ return f(); });
       benchmark([]{ return g(20); });
       benchmark([]{ return h(4, "Hello World"); });
       return 0;
    }
    

    ...ist nur noch ein nice to have....
    Wenn man in der benchmark-funktion noch einen Parameter anpassen will geht so etwas auch? Ein beispiel wäre hier benchm_v2(), dass natürlich so nicht geht:

    int test_func(int A, int B)
    {
    	return A + B;
    }
    
    
    void benchm(const std::function<int()>& fkt, std::string testStr)
    {
    	// [...]
    	int retval = fkt();
    	// [...]
    	std::cout << testStr << " --> " << retval << std::endl;
    }
    
    void benchm_v2(const std::function<int()>& fkt, int paramA, int paramB, std::string testStr)
    {
    	// [...]
    	int retval = fkt(paramA+123, paramB);
    	// [...]
    	std::cout << testStr << " --> " << retval << std::endl;
    }
    
    int main(int argc, char* argv[])
    {
    	int myA = 3;
    	int myB = 5;
    	std::string testStr = "A + B";
    
    	benchm([&] {return test_func(myA, myB);}, testStr); // --> geht
    	benchm_v2([&] {return test_func();}, myA, myB, testStr); // --> geht nicht
    
    	return 0;
    }
    

    Bzw gibt es Funktionszeiger, bei der die Parameterliste nicht angegeben werden muss? ...ich kenne von damals noch solche Konstrukte:

    int (*fptr)(int, int) = &test_func;
    fptr(myA, myB);
    

    ..aber da gebe ich ja auch die Parameterliste bereits an. 🤔

    ....erstmal einen Kaffee. 😅



  • @SBond sagte in verschiedene Funktionen benchmarken:

    benchm_v2([&] {return test_func();}, myA, myB, testStr); // --> geht nicht

    Du kannst doch die getemplatete Lösung von @DocShoe ohne std::function dafür verwenden. Was ist denn überhaupt der Sinn deines Lambdas in dem "geht nicht"-Fall?!

    Oder ändere
    void benchm_v2(const std::function<int()>& fkt, int paramA, int paramB, std::string testStr)
    in
    void benchm_v2(const std::function<int(int, int)>& fkt, int paramA, int paramB, std::string testStr)
    und
    benchm_v2([&] {return test_func();}, myA, myB, testStr); // --> geht nicht
    in
    benchm_v2(test_func, myA, myB, testStr); // --> geht



  • ahh..... vielen Dank. Stimmt geht beides 😅
    vielen Dank.

    Wegen dem Lambda.... Das hat im Beispiel keine nähere Bedeutung. Ich bin gerade dabei mich wieder etwas einzuüben.

    Nochmals vielen Dank für die Hilfe 🙂


Anmelden zum Antworten