Pomiar czasu wykonywania funkcji w C ++


147

Chcę się dowiedzieć, ile czasu zajmuje wykonanie określonej funkcji w moim programie w języku C ++ w systemie Linux . Następnie chcę dokonać porównania szybkości. Widziałem kilka funkcji czasu, ale skończyło się na tym z doładowania. Chrono:

process_user_cpu_clock, captures user-CPU time spent by the current process

Teraz nie jestem pewien, czy użyję powyższej funkcji, czy otrzymam jedyny czas, jaki procesor spędził na tej funkcji?

Po drugie, nie mogłem znaleźć żadnego przykładu użycia powyższej funkcji. Czy ktoś może mi pomóc, jak korzystać z powyższej funkcji?

PS: W tej chwili używam, std::chrono::system_clock::now()aby uzyskać czas w sekundach, ale daje to inne wyniki ze względu na różne obciążenie procesora za każdym razem.


2
Dla Linuksa: clock_gettime.. gcc definiuje inne zegary jako: typedef system_clock steady_clock; typedef system_clock high_resolution_clock;w Windows użyj QueryPerformanceCounter.
Brandon,

Czy to pytanie nie jest duplikatem tego, czy scenariusze sprawiają, że rozwiązania są inne?
Northerner

Mam dwie implementacje funkcji i chciałbym znaleźć, która działa lepiej.
Northerner

Bardzo ważne: upewnij się, że włączyłeś optymalizację . Niezoptymalizowany kod ma inne wąskie gardła niż normalny zoptymalizowany kod i nie mówi nic znaczącego. Pomoc optymalizacji pętli C dla końcowego przypisania (z wyłączoną optymalizacją kompilatora) . Ogólnie rzecz biorąc, mikrobenchmarking ma wiele pułapek, zwłaszcza niepowodzenie w wykonaniu najpierw pętli rozgrzewającej dla częstotliwości procesora i błędów stron: Idiomatyczny sposób oceny wydajności? . I ta odpowiedź
Peter Cordes

Zobacz także Jak zbadałbyś wydajność funkcji? dla Google Benchmark, który pozwala uniknąć wielu pułapek związanych z tworzeniem własnego mikroznaku. Również prosty test porównawczy pętli for () zajmuje tyle samo czasu z każdą pętlą związaną, aby uzyskać więcej informacji na temat interakcji optymalizacji z pętlami testów porównawczych i co z tym zrobić.
Peter Cordes

Odpowiedzi:


280

Jest to bardzo łatwa w użyciu metoda w C ++ 11. Musisz użyć std::chrono::high_resolution_clockz <chrono>nagłówka.

Użyj go w ten sposób:

#include <iostream>
#include <chrono>

void function()
{
    long long number = 0;

    for( long long i = 0; i != 2000000; ++i )
    {
       number += 5;
    }
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    function();
    auto t2 = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();

    std::cout << duration;
    return 0;
}

Będzie to mierzyć czas trwania funkcji.

UWAGA: Nie zawsze uzyskasz ten sam czas dla funkcji. Dzieje się tak, ponieważ procesor twojego komputera może być mniej lub bardziej używany przez inne procesy uruchomione na twoim komputerze, tak jak twój umysł może być bardziej lub mniej skoncentrowany podczas rozwiązywania zadań matematycznych. W ludzkim umyśle możemy zapamiętać rozwiązanie problemu matematycznego, ale dla komputera ten sam proces zawsze będzie czymś nowym; zatem, jak powiedziałem, nie zawsze uzyskasz ten sam wynik!


Kiedy używam tej funkcji, przy pierwszym uruchomieniu dało mi to 118440535 mikrosekund, a przy drugim uruchomieniu tej samej funkcji dało mi to 83221031 mikrosekund. Czy te dwa pomiary czasu nie powinny być równe, gdy mierzę czas trwania tylko tej funkcji?
Xara

2
Nie. Procesor twojego komputera może być używany mniej lub więcej. high_resolution_clockDaje czas fizyczną i prawdziwe, że funkcja zaczyna biec. Tak więc w pierwszym uruchomieniu Twój procesor był używany mniej niż w następnym. Przez „używany” mam na myśli to, jakie inne aplikacje wykorzystują procesor.
Victor

1
Tak, jeśli potrzebujesz średniego czasu, to dobry sposób, aby to uzyskać. wykonaj trzy przebiegi i oblicz średnią.
Victor

4
Czy mógłbyś przesłać kod pocztowy bez „używania przestrzeni nazw” w ogóle. Dzięki temu łatwiej jest zobaczyć, co pochodzi skąd.
Bałwan

1
Czy to nie powinno być steady_clock? Czy nie jest możliwe, high_resolution_clockaby zegar był niemonotoniczny?
Gillespie

18

Oto funkcja, która będzie mierzyć czas wykonania dowolnej funkcji przekazanej jako argument:

#include <chrono>
#include <utility>

typedef std::chrono::high_resolution_clock::time_point TimeVar;

#define duration(a) std::chrono::duration_cast<std::chrono::nanoseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()

template<typename F, typename... Args>
double funcTime(F func, Args&&... args){
    TimeVar t1=timeNow();
    func(std::forward<Args>(args)...);
    return duration(timeNow()-t1);
}

Przykładowe użycie:

#include <iostream>
#include <algorithm>

typedef std::string String;

//first test function doing something
int countCharInString(String s, char delim){
    int count=0;
    String::size_type pos = s.find_first_of(delim);
    while ((pos = s.find_first_of(delim, pos)) != String::npos){
        count++;pos++;
    }
    return count;
}

//second test function doing the same thing in different way
int countWithAlgorithm(String s, char delim){
    return std::count(s.begin(),s.end(),delim);
}


int main(){
    std::cout<<"norm: "<<funcTime(countCharInString,"precision=10",'=')<<"\n";
    std::cout<<"algo: "<<funcTime(countWithAlgorithm,"precision=10",'=');
    return 0;
}

Wynik:

norm: 15555
algo: 2976

3
@ RestlessC0bra: Jest zdefiniowana w implementacji, high_resolution_clockmoże być aliasem system_clock(zegar ścienny) steady_clocklub trzecim niezależnym zegarem. Zobacz szczegóły tutaj . Dla zegara procesora std::clockmoże być używany
Jahid

2
Dwa makra i globalna typedef - z których żadne nie jest bezpieczne dla pojedynczego naciśnięcia klawisza - to z pewnością nic, co nazwałbym eleganckim. niewygodne), kiedy można po prostu wymagać umieszczenia kodu czasowego w lambdzie. Ale cóż, o ile przekazywanie argumentów jest opcjonalne.
MikeMB

2
I to jest uzasadnienie łamania wszystkich wskazówek dotyczących nazewnictwa makr? Nie prefiksujesz ich, nie używasz wielkich liter, wybierasz bardzo popularną nazwę, która ma duże prawdopodobieństwo kolizji z jakimś lokalnym symbolem, a przede wszystkim: Dlaczego w ogóle używasz makra (zamiast funkcji )? A skoro już o tym mowa: dlaczego w pierwszej kolejności zwracasz czas trwania jako podwójną reprezentację nanosekund? Prawdopodobnie powinniśmy się zgodzić, że się nie zgadzamy. Moja pierwotna opinia brzmi: „To nie jest to, co nazwałbym eleganckim kodem”.
MikeMB

2
@MikeMB: Słuszna uwaga, zrobienie tego nagłówka byłoby zdecydowanie złym pomysłem. Ostatecznie to tylko przykład, jeśli masz złożone potrzeby, musisz pomyśleć o standardowych praktykach i odpowiednio dostosować kod. Na przykład, pisząc kod, staram się, aby było to dla mnie wygodne, gdy znajduje się w pliku cpp, nad którym teraz pracuję, ale kiedy nadejdzie czas, aby przenieść go w inne miejsce, podejmuję wszelkie niezbędne kroki, aby uczynić go solidnym, aby nie musieć spójrz na to jeszcze raz. I myślę, że każdy programista, który nie jest kompletnym noobem, myśli szeroko, kiedy nadejdzie czas. Mam nadzieję, że wyjaśniłem mój punkt: D.
Jahid

2
@Jahid: Dzięki. W takim przypadku uważaj moje komentarze za nieważne i nieważne.
MikeMB

9

prosty program do znalezienia czasu wykonania funkcji.

#include <iostream>
#include <ctime> // time_t
#include <cstdio>

void function()
{
     for(long int i=0;i<1000000000;i++)
     {
        // do nothing
     }
}

int main()
{

time_t begin,end; // time_t is a datatype to store time values.

time (&begin); // note time before execution
function();
time (&end); // note time after execution

double difference = difftime (end,begin);
printf ("time taken for function() %.2lf seconds.\n", difference );

return 0;
}

8
jest bardzo niedokładny, pokazuje tylko sekundy, ale bez milisekund
user25

7

W książce Scotta Meyersa znalazłem przykład uniwersalnego generycznego wyrażenia lambda, którego można użyć do pomiaru czasu wykonywania funkcji. (C ++ 14)

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = std::chrono::high_resolution_clock::now();
        // function invocation using perfect forwarding
        std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        // get time after function invocation
        const auto& stop = std::chrono::high_resolution_clock::now();
        return stop - start;
     };

Problem polega na tym, że mierzysz tylko jedno wykonanie, więc wyniki mogą być bardzo różne. Aby uzyskać wiarygodny wynik, należy zmierzyć dużą liczbę wykonań. Według wykładu Andrei Alexandrescu na konferencji code :: dive 2015 - Writing Fast Code I:

Zmierzony czas: tm = t + tq + tn + to

gdzie:

tm - zmierzony (obserwowany) czas

t - rzeczywisty czas zainteresowania

tq - czas dodany przez szum kwantyzacji

tn - czas dodawany przez różne źródła hałasu

to - czas narzutu (pomiar, pętla, wywołanie funkcji)

Zgodnie z tym, co powiedział w dalszej części wykładu, jako wynik należy przyjąć minimum tej dużej liczby egzekucji. Zachęcam do obejrzenia wykładu, w którym wyjaśnia dlaczego.

Jest też bardzo dobra biblioteka Google - https://github.com/google/benchmark . Ta biblioteka jest bardzo prosta w użyciu i potężna. Możesz sprawdzić niektóre wykłady Chandlera Carrutha na youtube, gdzie korzysta z tej biblioteki w praktyce. Na przykład CppCon 2017: Chandler Carruth „Going Nowhere Faster”;

Przykładowe użycie:

#include <iostream>
#include <chrono>
#include <vector>
auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = high_resolution_clock::now();
        // function invocation using perfect forwarding
        for(auto i = 0; i < 100000/*largeNumber*/; ++i) {
            std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        }
        // get time after function invocation
        const auto& stop = high_resolution_clock::now();
        return (stop - start)/100000/*largeNumber*/;
     };

void f(std::vector<int>& vec) {
    vec.push_back(1);
}

void f2(std::vector<int>& vec) {
    vec.emplace_back(1);
}
int main()
{
    std::vector<int> vec;
    std::vector<int> vec2;
    std::cout << timeFuncInvocation(f, vec).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec2).count() << std::endl;
    std::vector<int> vec3;
    vec3.reserve(100000);
    std::vector<int> vec4;
    vec4.reserve(100000);
    std::cout << timeFuncInvocation(f, vec3).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec4).count() << std::endl;
    return 0;
}

EDYCJA: Oczywiście zawsze musisz pamiętać, że Twój kompilator może coś zoptymalizować lub nie. W takich przypadkach przydatne mogą być narzędzia takie jak perf.


Interesujące - jaka jest korzyść z używania tutaj lambdy zamiast szablonu funkcji?
user48956

1
Główną różnicą byłoby to, że jest to obiekt wywoływalny, ale rzeczywiście można uzyskać coś bardzo podobnego za pomocą szablonu wariadycznego i std :: result_of_t.
Krzysztof Sommerfeld

@KrzysztofSommerfeld Jak to zrobić dla metod funkcyjnych, kiedy przekażę timing (Object.Method1) zwraca błąd "niestandardowa składnia; użyj '&', aby utworzyć wskaźnik do elementu członkowskiego"
RobinAtTech

timeFuncInvocation ([& objectName] (auto && ... args) {objectName.methodName (std :: forward <decltype (args)> (args) ...);}, arg1, arg2, ...); lub ommit & sign przed objectName (wtedy będziesz miał kopię obiektu)
Krzysztof Sommerfeld

4

Łatwy sposób na starsze C ++ lub C:

#include <time.h> // includes clock_t and CLOCKS_PER_SEC

int main() {

    clock_t start, end;

    start = clock();
    // ...code to measure...
    end = clock();

    double duration_sec = double(end-start)/CLOCKS_PER_SEC;
    return 0;
}

Dokładność pomiaru czasu w sekundach wynosi 1.0/CLOCKS_PER_SEC


1
To nie jest przenośne. Mierzy czas procesora w systemie Linux i czas zegara w systemie Windows.
BugSquasher

czas rozpoczęcia i zakończenia są zawsze takie same, pomimo dodania tablicy 512 elementów ..... pod Win64 / Visual Studio 17
Maverick

2
  • Jest to bardzo łatwa w użyciu metoda w C ++ 11.
  • Możemy użyć std :: chrono :: high_resolution_clock z nagłówka
  • Możemy napisać metodę wypisującą czas wykonania metody w czytelnej formie.

Na przykład, aby znaleźć wszystkie liczby pierwsze od 1 do 100 milionów, zajmuje to około 1 minuty i 40 sekund. Więc czas wykonania jest drukowany jako:

Execution Time: 1 Minutes, 40 Seconds, 715 MicroSeconds, 715000 NanoSeconds

Kod jest tutaj:

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

typedef high_resolution_clock Clock;
typedef Clock::time_point ClockTime;

void findPrime(long n, string file);
void printExecutionTime(ClockTime start_time, ClockTime end_time);

int main()
{
    long n = long(1E+8);  // N = 100 million

    ClockTime start_time = Clock::now();

    // Write all the prime numbers from 1 to N to the file "prime.txt"
    findPrime(n, "C:\\prime.txt"); 

    ClockTime end_time = Clock::now();

    printExecutionTime(start_time, end_time);
}

void printExecutionTime(ClockTime start_time, ClockTime end_time)
{
    auto execution_time_ns = duration_cast<nanoseconds>(end_time - start_time).count();
    auto execution_time_ms = duration_cast<microseconds>(end_time - start_time).count();
    auto execution_time_sec = duration_cast<seconds>(end_time - start_time).count();
    auto execution_time_min = duration_cast<minutes>(end_time - start_time).count();
    auto execution_time_hour = duration_cast<hours>(end_time - start_time).count();

    cout << "\nExecution Time: ";
    if(execution_time_hour > 0)
    cout << "" << execution_time_hour << " Hours, ";
    if(execution_time_min > 0)
    cout << "" << execution_time_min % 60 << " Minutes, ";
    if(execution_time_sec > 0)
    cout << "" << execution_time_sec % 60 << " Seconds, ";
    if(execution_time_ms > 0)
    cout << "" << execution_time_ms % long(1E+3) << " MicroSeconds, ";
    if(execution_time_ns > 0)
    cout << "" << execution_time_ns % long(1E+6) << " NanoSeconds, ";
}

1

Jeśli chcesz zaoszczędzić czas i linie kodu, możesz zmierzyć czas wykonywania funkcji makrem jednowierszowym:

a) Zaimplementuj klasę pomiaru czasu, jak już zasugerowano powyżej (oto moja implementacja dla Androida):

class MeasureExecutionTime{
private:
    const std::chrono::steady_clock::time_point begin;
    const std::string caller;
public:
    MeasureExecutionTime(const std::string& caller):caller(caller),begin(std::chrono::steady_clock::now()){}
    ~MeasureExecutionTime(){
        const auto duration=std::chrono::steady_clock::now()-begin;
        LOGD("ExecutionTime")<<"For "<<caller<<" is "<<std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()<<"ms";
    }
};

b) Dodaj dogodnym makro, które wykorzystuje aktualną nazwę funkcjonować jako TAG (za pomocą makra tutaj jest ważne, bo inaczej __FUNCTION__oceni aby MeasureExecutionTimezamiast funkcji, którą wanto do środka

#ifndef MEASURE_FUNCTION_EXECUTION_TIME
#define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__FUNCTION__);
#endif

c) Napisz makro na początku funkcji, którą chcesz zmierzyć. Przykład:

 void DecodeMJPEGtoANativeWindowBuffer(uvc_frame_t* frame_mjpeg,const ANativeWindow_Buffer& nativeWindowBuffer){
        MEASURE_FUNCTION_EXECUTION_TIME
        // Do some time-critical stuff 
}

Co da w wyniku następujący wynik:

ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms

Zauważ, że to (podobnie jak wszystkie inne sugerowane rozwiązania) będzie mierzyć czas między wywołaniem funkcji a zwróceniem, niekoniecznie czas wykonywania funkcji przez procesor. Jeśli jednak nie dasz programowi planującemu żadnej zmiany, aby zawiesić działający kod przez wywołanie funkcji sleep () lub podobnej, nie ma różnicy między.


0

Oto doskonały szablon klasy z samym nagłówkiem do pomiaru czasu, który upłynął od funkcji lub dowolnego bloku kodu:

#ifndef EXECUTION_TIMER_H
#define EXECUTION_TIMER_H

template<class Resolution = std::chrono::milliseconds>
class ExecutionTimer {
public:
    using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
                                     std::chrono::high_resolution_clock,
                                     std::chrono::steady_clock>;
private:
    const Clock::time_point mStart = Clock::now();

public:
    ExecutionTimer() = default;
    ~ExecutionTimer() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Destructor Elapsed: "
                  << std::chrono::duration_cast<Resolution>( end - mStart ).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }    

    inline void stop() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Stop Elapsed: "
                  << std::chrono::duration_cast<Resolution>(end - mStart).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }

}; // ExecutionTimer

#endif // EXECUTION_TIMER_H

Oto kilka zastosowań:

int main() {
    { // empty scope to display ExecutionTimer's destructor's message
         // displayed in milliseconds
         ExecutionTimer<std::chrono::milliseconds> timer;

         // function or code block here

         timer.stop();

    } 

    { // same as above
        ExecutionTimer<std::chrono::microseconds> timer;

        // code block here...

        timer.stop();
    }

    {  // same as above
       ExecutionTimer<std::chrono::nanoseconds> timer;

       // code block here...

       timer.stop();

    }

    {  // same as above
       ExecutionTimer<std::chrono::seconds> timer;

       // code block here...

       timer.stop();

    }              

    return 0;
}

Ponieważ klasa jest szablonem, możemy naprawdę łatwo określić sposób, w jaki chcemy mierzyć i wyświetlać nasz czas. Jest to bardzo poręczny szablon klasy użytkowej do wykonywania oznaczeń laboratoryjnych i jest bardzo łatwy w użyciu.


Osobiście stop()funkcja członkowska nie jest potrzebna, ponieważ destruktor zatrzymuje licznik czasu za Ciebie.
Casey,

@Casey Projekt klasy niekoniecznie potrzebuje funkcji stop, jednak istnieje z określonego powodu. Konstrukcja domyślna podczas tworzenia obiektu przed test codeuruchomieniem licznika czasu. Następnie po test codejawnym użyciu obiektu timera i wywołaniu jego metody stop. Musisz wywołać go ręcznie, kiedy chcesz, aby stoptimer. Klasa nie przyjmuje żadnych parametrów. Ponadto, jeśli użyłeś tej klasy, tak jak pokazałem, zobaczysz, że jest minimalny upływ czasu między wywołaniem obj.stopa jego destructor.
Francis Cugler

@Casey ... Pozwala to również na posiadanie wielu obiektów czasowych w tym samym zakresie, nie tak, że ktoś by tego naprawdę potrzebował, ale po prostu inna opłacalna opcja.
Francis Cugler

Tego przykładu nie da się skompilować w przedstawionej formie. Błąd jest związany z „brakiem dopasowania dla operatora << ...”!
Celdor

@Celdor czy musisz odpowiednie obejmuje; takie jak <chrono>?
Francis Cugler,

0

W steady_clockprzeciwieństwie do tego, zalecam używanie tego, co jest gwarantowane, aby było monotoniczne high_resolution_clock.

#include <iostream>
#include <chrono>

using namespace std;

unsigned int stopwatch()
{
    static auto start_time = chrono::steady_clock::now();

    auto end_time = chrono::steady_clock::now();
    auto delta    = chrono::duration_cast<chrono::microseconds>(end_time - start_time);

    start_time = end_time;

    return delta.count();
}

int main() {
  stopwatch(); //Start stopwatch
  std::cout << "Hello World!\n";
  cout << stopwatch() << endl; //Time to execute last line
  for (int i=0; i<1000000; i++)
      string s = "ASDFAD";
  cout << stopwatch() << endl; //Time to execute for loop
}

Wynik:

Hello World!
62
163514

0

Możesz mieć prostą klasę, której można użyć do tego rodzaju pomiarów.

class duration_printer {
public:
    duration_printer() : __start(std::chrono::high_resolution_clock::now()) {}
    ~duration_printer() {
        using namespace std::chrono;
        high_resolution_clock::time_point end = high_resolution_clock::now();
        duration<double> dur = duration_cast<duration<double>>(end - __start);
        std::cout << dur.count() << " seconds" << std::endl;
    }
private:
    std::chrono::high_resolution_clock::time_point __start;
};

Jedyne, co musisz zrobić, to utworzyć obiekt w swojej funkcji na początku tej funkcji

void veryLongExecutingFunction() {
    duration_calculator dc;
    for(int i = 0; i < 100000; ++i) std::cout << "Hello world" << std::endl;
}

int main() {
    veryLongExecutingFunction();
    return 0;
}

i to wszystko. Klasę można modyfikować, aby odpowiadała Twoim wymaganiom.


0

Ponieważ żadna z udzielonych odpowiedzi nie jest bardzo dokładna ani nie daje powtarzalnych wyników, zdecydowałem się dodać link do mojego kodu, który ma precyzję poniżej nanosekund i statystyki naukowe.

Zauważ, że zadziała to tylko do pomiaru kodu, którego wykonanie zajmuje (bardzo) krótki czas (czyli kilka cykli zegara do kilku tysięcy): jeśli działają tak długo, że prawdopodobnie zostaną przerwane przez jakieś -heh- przerwanie , wtedy oczywiście nie jest możliwe podanie powtarzalnego i dokładnego wyniku; Konsekwencją tego jest to, że pomiar nigdy się nie kończy: a mianowicie kontynuuje pomiar, dopóki nie uzyska statystycznej 99,9% pewności, że ma prawidłową odpowiedź, co nigdy się nie zdarza na maszynie, na której działają inne procesy, gdy kod trwa zbyt długo.

https://github.com/CarloWood/cwds/blob/master/benchmark.h#L40

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.