2015-07-13 22 views
5

Sono interessato a cronometrare il tempo di esecuzione di una funzione libera o di una funzione membro (modello o meno). Chiamare TheFunc la funzione in questione, la sua richiesta di essereTiming in modo elegante in C++

TheFunc(/*parameters*/); 

o

ReturnType ret = TheFunc(/*parameters*/); 

Naturalmente potrei avvolgere queste chiamate di funzione come segue:

double duration = 0.0 ; 
std::clock_t start = std::clock(); 
TheFunc(/*parameters*/); 
duration = static_cast<double>(std::clock() - start)/static_cast<double>(CLOCKS_PER_SEC); 

o

double duration = 0.0 ; 
std::clock_t start = std::clock(); 
ReturnType ret = TheFunc(/*parameters*/); 
duration = static_cast<double>(std::clock() - start)/static_cast<double>(CLOCKS_PER_SEC); 

ma mi piacerebbe d o qualcosa di più elegante di questo, vale a dire (e da ora in poi mi atterrò al tipo di ritorno void) come segue:

Timer thetimer ; 
double duration = 0.0; 
thetimer(*TheFunc)(/*parameters*/, duration); 

dove Timer è una classe di temporizzazione che vorrei progettare e che mi permettesse di scrivere il codice precedente, in modo tale che dopo l'exectution dell'ultima riga di codice precedente il doppio della durata conterrà il tempo di esecuzione di

TheFunc(/*parameters*/); 

ma non vedo come fare questo, né se la sintassi/soluzione che obiettivo è ottimale ...

+0

Non capisco davvero cosa mi stai chiedendo. E 'se è meglio usare un timer asincrono? –

+0

Modificherò la mia domanda. –

+0

Modifica appena eseguita. –

risposta

6

Con variadic modello, si può fare:

template <typename F, typename ... Ts> 
double Time_function(F&& f, Ts&&...args) 
{ 
    std::clock_t start = std::clock(); 
    std::forward<F>(f)(std::forward<Ts>(args)...); 
    return static_cast<double>(std::clock() - start)/static_cast<double>(CLOCKS_PER_SEC); 
} 
+0

Questa è una buona idea. Puoi dare un'occhiata alla modifica della mia risposta, dove prendo la tua idea e la faccio funzionare con 'std :: chrono'. – Escualo

4

mi piace molto boost::cpu_timer::auto_cpu_timer, e quando non posso usare spinta ho semplicemente incidere il mio proprio:

#include <cmath> 
#include <string> 
#include <chrono> 
#include <iostream> 

class AutoProfiler { 
public: 
    AutoProfiler(std::string name) 
     : m_name(std::move(name)), 
     m_beg(std::chrono::high_resolution_clock::now()) { } 
    ~AutoProfiler() { 
    auto end = std::chrono::high_resolution_clock::now(); 
    auto dur = std::chrono::duration_cast<std::chrono::microseconds>(end - m_beg); 
    std::cout << m_name << " : " << dur.count() << " musec\n"; 
    } 
private: 
    std::string m_name; 
    std::chrono::time_point<std::chrono::high_resolution_clock> m_beg; 
}; 

void foo(std::size_t N) { 
    long double x {1.234e5}; 
    for(std::size_t k = 0; k < N; k++) { 
    x += std::sqrt(x); 
    } 
} 


int main() { 
    { 
    AutoProfiler p("N = 10"); 
    foo(10); 
    } 

    { 
    AutoProfiler p("N = 1,000,000"); 
    foo(1000000); 
    } 

} 

Questo timer funziona grazie a Raii. Quando si crea l'oggetto in un ambito, si memorizza il punto di tempo in quel momento. Quando si lascia l'ambito (ovvero, al corrispondente }), il timer memorizza prima il punto di tempo, quindi calcola il numero di zecche (che è possibile convertire in una durata leggibile dall'uomo) e infine lo stampa sullo schermo.

Ovviamente, boost::timer::auto_cpu_timer è molto più elaborato della mia semplice implementazione, ma spesso trovo la mia implementazione più che sufficiente per i miei scopi.

Esempio correre nel mio computer:

$ g++ -o example example.com -std=c++14 -Wall -Wextra 
$ ./example 
N = 10 : 0 musec 
N = 1,000,000 : 10103 musec 

EDIT

Mi è piaciuto molto l'attuazione suggerito da @ Jarod42. L'ho modificato un po 'per offrire una certa flessibilità sulle "unità" desiderate dell'output.

Per impostazione predefinita, restituisce il numero di microsecondi trascorsi (un numero intero, normalmente std::size_t), ma è possibile richiedere che l'output sia in qualsiasi durata di propria scelta.

Penso che sia un approccio più flessibile rispetto a quello che ho suggerito in precedenza perché ora posso fare altre cose come prendere le misure e memorizzarle in un contenitore (come faccio nell'esempio).

Grazie a @ Jarod42 per l'ispirazione.

#include <cmath> 
#include <string> 
#include <chrono> 
#include <algorithm> 
#include <iostream> 

template<typename Duration = std::chrono::microseconds, 
     typename F, 
     typename ... Args> 
typename Duration::rep profile(F&& fun, Args&&... args) { 
    const auto beg = std::chrono::high_resolution_clock::now(); 
    std::forward<F>(fun)(std::forward<Args>(args)...); 
    const auto end = std::chrono::high_resolution_clock::now(); 
    return std::chrono::duration_cast<Duration>(end - beg).count(); 
} 

void foo(std::size_t N) { 
    long double x {1.234e5}; 
    for(std::size_t k = 0; k < N; k++) { 
    x += std::sqrt(x); 
    } 
} 

int main() { 
    std::size_t N { 1000000 }; 

    // profile in default mode (microseconds) 
    std::cout << "foo(1E6) takes " << profile(foo, N) << " microseconds" << std::endl; 

    // profile in custom mode (e.g, milliseconds) 
    std::cout << "foo(1E6) takes " << profile<std::chrono::milliseconds>(foo, N) << " milliseconds" << std::endl; 

    // To create an average of `M` runs we can create a vector to hold 
    // `M` values of the type used by the clock representation, fill 
    // them with the samples, and take the average 
    std::size_t M {100}; 
    std::vector<typename std::chrono::milliseconds::rep> samples(M); 
    for(auto & sample : samples) { 
    sample = profile(foo, N); 
    } 
    auto avg = std::accumulate(samples.begin(), samples.end(), 0)/static_cast<long double>(M); 
    std::cout << "average of " << M << " runs: " << avg << " microseconds" << std::endl; 
} 

uscita (compilato con g++ example.cpp -std=c++14 -Wall -Wextra -O3):

foo(1E6) takes 10073 microseconds 
foo(1E6) takes 10 milliseconds 
average of 100 runs: 10068.6 microseconds 
0

io sono un fan di utilizzare involucri RAII per questo tipo di cose.

Il seguente esempio è un po 'prolisso, ma è più flessibile che funziona con gli ambiti arbitrarie invece di essere limitato a una singola chiamata di funzione:

class timing_context { 
public: 
    std::map<std::string, double> timings; 
}; 

class timer { 
public: 
    timer(timing_context& ctx, std::string name) 
    : ctx(ctx), 
     name(name), 
     start(std::clock()) {} 

    ~timer() { 
    ctx.timings[name] = static_cast<double>(std::clock() - start)/static_cast<double>(CLOCKS_PER_SEC); 
    } 

    timing_context& ctx; 
    std::string name; 
    std::clock_t start; 
}; 

timing_context ctx; 

int main() { 
    timer_total(ctx, "total"); 
    { 
    timer t(ctx, "foo"); 
    // Do foo 
    } 

    { 
    timer t(ctx, "bar"); 
    // Do bar 
    } 
    // Access ctx.timings 
} 

il lato negativo è che si potrebbe finire con un sacco di ambiti che servono solo a distruggere l'oggetto temporale.

Questo potrebbe o potrebbe non soddisfare le vostre esigenze in quanto la vostra richiesta è stata un po 'vaga, ma illustra come l'utilizzo della semantica RAII può rendere piacevole un codice riutilizzabile e pulito. Probabilmente può essere modificato per sembrare molto meglio!

1

Puoi farlo in modo MatLab. E 'molto vecchia scuola, ma semplice è spesso buona:

tic(); 
    a = f(c); 
    toc(); //print to stdout, or 
    auto elapsed = toc(); //store in variable 

tic() e toc() può lavorare per una variabile globale. Se ciò non è sufficiente, è possibile creare variabili locali con alcune macro-magia:

tic(A); 
    a = f(c); 
    toc(A);