Medición del tiempo de ejecución de una función en C ++

138

Quiero saber cuánto tiempo tarda una determinada función en ejecutar mi programa C ++ en Linux . Después, quiero hacer una comparación de velocidad. Vi varias funciones de tiempo pero terminé con esto desde boost. Crono:

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

Ahora, no estoy claro si uso la función anterior, ¿tendré el único tiempo que la CPU pasó en esa función?

En segundo lugar, no pude encontrar ningún ejemplo de uso de la función anterior. ¿Alguien puede ayudarme a usar la función anterior?

PD: En este momento, estoy usando std::chrono::system_clock::now()para obtener el tiempo en segundos, pero esto me da resultados diferentes debido a la carga de CPU diferente cada vez.

Xara
fuente
2
Para uso de Linux: clock_gettime.. gcc define otros relojes como: typedef system_clock steady_clock; typedef system_clock high_resolution_clock;en Windows, use QueryPerformanceCounter.
Brandon
¿No es esta pregunta un duplicado de esta o los escenarios hacen que las soluciones sean diferentes?
norteño
Tengo dos implementaciones de una función y me gustaría encontrar cuál funciona mejor.
norteño
Muy importante: asegúrese de habilitar la optimización . El código no optimizado tiene diferentes cuellos de botella que el código optimizado normal, y no le dice nada significativo. Ayuda de optimización de bucle C para la asignación final (con la optimización del compilador deshabilitada) . Y, en general, el microbenchmarking tiene muchos inconvenientes, especialmente la falta de hacer un ciclo de calentamiento primero para la frecuencia de la CPU y las fallas de página: ¿ forma idiomática de evaluación del rendimiento? . Y esta respuesta
Peter Cordes,
Consulte también ¿Cómo evaluaría el rendimiento de una función? para Google Benchmark que evita muchas de las trampas de rodar su propio microbenchmark. También el benchmark Simple for () loop toma el mismo tiempo con cualquier loop enlazado para obtener más información sobre cómo la optimización interactúa con los bucles benchmark y qué hacer al respecto.
Peter Cordes

Respuestas:

264

Es un método muy fácil de usar en C ++ 11. Tienes que usar std::chrono::high_resolution_clockdesde el <chrono>encabezado.

Úselo así:

#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;
}

Esto medirá la duración de la función.

NOTA: No siempre obtendrá el mismo tiempo para una función. Esto se debe a que la CPU de su máquina puede ser menos o más utilizada por otros procesos que se ejecutan en su computadora, así como su mente puede estar más o menos concentrada cuando resuelve un ejercicio matemático. En la mente humana, podemos recordar la solución de un problema matemático, pero para una computadora el mismo proceso siempre será algo nuevo; así, como dije, ¡no siempre obtendrás el mismo resultado!

Víctor
fuente
Cuando uso esta función, en la primera ejecución me dio 118440535 microsegundos y en la segunda ejecución de la misma función me dio 83221031 microsegundos. ¿No deberían ser iguales las dos mediciones de tiempo cuando estoy midiendo la duración de esa función solamente?
Xara
1
No. El procesador de su computadora se puede usar menos o más. El high_resolution_clockle dará el tiempo físico y real que su función tarda en ejecutarse. Entonces, en su primera ejecución, su CPU se estaba usando menos que en la siguiente. Por "usado" me refiero a qué otro trabajo de aplicación usa la CPU.
Victor
1
Sí, si necesita el promedio de tiempo, esa es una buena manera de obtenerlo. toma tres carreras y calcula el promedio.
Victor
3
¿Podría publicar código sin "usar el espacio de nombres" en general. Hace que sea más fácil ver de dónde viene.
Snowman
1
¿No debería ser esto un steady_clock? ¿No es posible que high_resolution_clockpueda ser un reloj no monótono?
Gillespie
15

Aquí hay una función que medirá el tiempo de ejecución de cualquier función pasada como argumento:

#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);
}

Ejemplo de uso:

#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;
}

Salida:

norm: 15555
algo: 2976
Jahid
fuente
2
@ RestlessC0bra: su implementación está definida, high_resolution_clockpuede ser un alias de system_clock(reloj de pared) steady_clocko un tercer reloj independiente. Ver detalles aquí . Para el reloj de la CPU, std::clockse puede usar
Jahid
2
Dos macros y una definición de tipo global, ninguna de las cuales es segura con una sola pulsación de tecla, no es nada que yo llamaría elegante. También pasar un objeto de función y reenviar perfectamente los argumentos por separado es un poco excesivo (y en el caso de funciones sobrecargadas incluso inconveniente), cuando solo puede requerir que el código cronometrado se coloque en una lambda. Pero bueno, siempre que pasar argumentos sea opcional.
MikeMB
2
¿Y esta es una justificación para violar todas y cada una de las pautas sobre el nombramiento de macros? No los prefijas, no usas letras mayúsculas, eliges un nombre muy común que tiene una alta probabilidad de colisionar con algún símbolo local y, sobre todo: ¿Por qué estás usando una macro en lugar de una función? )? Y mientras estamos en eso: ¿por qué devuelve la duración como un doble que representa nanosegundos en primer lugar? Probablemente deberíamos estar de acuerdo en que no estamos de acuerdo. Mi opinión original es: "Esto no es lo que yo llamaría código elegante".
MikeMB
1
El problema es que no tienen ámbito. Lo que me preocupa es que esas macros terminen en un archivo de encabezado que se incluya (tal vez indirectamente como parte de una biblioteca) en mi código. Si desea tener una idea de lo que sucede si los nombres comunes se usan para macros, incluir windows.hen un proyecto de C ++ no trivial. En assertprimer lugar, "quod licet iovi non licet bovi";). En segundo lugar, no todas las decisiones en la biblioteca estándar (que a veces datan de décadas) se consideran una buena idea según los estándares modernos. Hay una razón por la cual el diseñador de módulos de C ++ se esfuerza mucho por no exportar macros por defecto.
MikeMB
2
@Jahid: Gracias. En ese caso, considere mis comentarios nulos y sin valor.
MikeMB
9

programa simple para encontrar el tiempo de ejecución de una función.

#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;
}
Abdullah Farweez
fuente
66
es muy impreciso, muestra solo segundos, pero no milisegundos
usuario25
7

En el libro de Scott Meyers encontré un ejemplo de expresión lambda genérica universal que se puede usar para medir el tiempo de ejecución de funciones. (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;
     };

El problema es que solo mide una ejecución, por lo que los resultados pueden ser muy diferentes. Para obtener un resultado confiable, debe medir un gran número de ejecuciones. Según la conferencia de Andrei Alexandrescu en la conferencia code :: dive 2015 - Writing Fast Code I:

Tiempo medido: tm = t + tq + tn + to

dónde:

tm - tiempo medido (observado)

t - el tiempo real de interés

tq: tiempo agregado por el ruido de cuantización

tn - tiempo agregado por varias fuentes de ruido

a - tiempo de sobrecarga (medición, bucle, funciones de llamada)

De acuerdo con lo que dijo más adelante en la conferencia, debe tomar un mínimo de esta gran cantidad de ejecución como resultado. Os animo a mirar la conferencia en la que explica por qué.

También hay una muy buena biblioteca de google: https://github.com/google/benchmark . Esta biblioteca es muy simple de usar y poderosa. Puede consultar algunas conferencias de Chandler Carruth en YouTube donde está usando esta biblioteca en la práctica. Por ejemplo, CppCon 2017: Chandler Carruth "Going Nowhere Faster";

Ejemplo de uso:

#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;
}

EDITAR: Por supuesto, siempre debe recordar que su compilador puede optimizar algo o no. Herramientas como perf pueden ser útiles en tales casos.

Krzysztof Sommerfeld
fuente
Interesante: ¿cuál es el beneficio de usar una lambda aquí sobre una plantilla de función?
user48956
1
La principal diferencia sería que es un objeto invocable pero, de hecho, puede obtener algo muy similar con la plantilla variadic y std :: result_of_t.
Krzysztof Sommerfeld
@KrzysztofSommerfeld Cómo hacer esto para los métodos de función, cuando paso el tiempo (Object.Method1) devuelve el error "sintaxis no estándar; use '&' para crear un puntero al miembro"
RobinAtTech
timeFuncInvocation ([& objectName] (auto && ... args) {objectName.methodName (std :: forward <decltype (args)> (args) ...);}, arg1, arg2, ...); u omitir y firmar antes de objectName (entonces tendrás una copia del objeto)
Krzysztof Sommerfeld
4

Manera fácil para C ++ o C anteriores:

#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;
}

La precisión del tiempo en segundos es 1.0/CLOCKS_PER_SEC

v.chaplin
fuente
1
Esto no es portátil. Mide el tiempo del procesador en Linux y el tiempo de reloj en Windows.
BugSquasher
2
  • Es un método muy fácil de usar en C ++ 11.
  • Podemos usar std :: chrono :: high_resolution_clock desde el encabezado
  • Podemos escribir un método para imprimir el tiempo de ejecución del método en una forma muy legible.

Por ejemplo, para encontrar todos los números primos entre 1 y 100 millones, se tarda aproximadamente 1 minuto y 40 segundos. Entonces el tiempo de ejecución se imprime como:

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

El código está aquí:

#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, ";
}
Pratik Patil
fuente
0

Aquí hay una excelente plantilla de clase de solo encabezado para medir el tiempo transcurrido de una función o cualquier bloque de código:

#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

Aquí hay algunos usos:

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;
}

Como la clase es una plantilla, podemos especificar fácilmente cómo queremos medir y mostrar nuestro tiempo. Esta es una plantilla de clase de utilidad muy útil para hacer marcas de banco y es muy fácil de usar.

Francis Cugler
fuente
Personalmente, la stop()función miembro no es necesaria porque el destructor detiene el temporizador por usted.
Casey
@Casey El diseño de la clase no necesita necesariamente la función de parada, sin embargo, está ahí por una razón específica. La construcción predeterminada al crear el objeto antes de que test codecomience el temporizador. Luego, después de test codeusar explícitamente el objeto del temporizador y llamar a su método de detención. Tienes que invocarlo manualmente cuando quieras al stoptemporizador. La clase no toma ningún parámetro. Además, si utilizó esta clase tal como lo he mostrado, verá que hay un mínimo de tiempo entre la llamada obj.stopy su destructor.
Francis Cugler
@Casey ... Esto también permite tener múltiples objetos de temporizador dentro del mismo alcance, no es que uno realmente lo necesite, sino solo otra opción viable.
Francis Cugler
Este ejemplo no se puede compilar en la forma presentada. ¡El error está relacionado con "no coincide con el operador << ..."!
Celdor
@Celdor tiene que incluir apropiado; tales como <chrono>?
Francis Cugler
0

Recomiendo usar el steady_clockque es voluntario para ser monótono, a diferencia 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
}

Salida:

Hello World!
62
163514
Gillespie
fuente
0

Puede tener una clase simple que se puede utilizar para este tipo de mediciones.

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;
};

Lo único que debe hacer es crear un objeto en su función al comienzo de esa función

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

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

y eso es. La clase se puede modificar para adaptarse a sus requisitos.

arsdever
fuente
0

Como ninguna de las respuestas proporcionadas son muy precisas o dan resultados reproducibles, decidí agregar un enlace a mi código que tenga precisión y estadísticas científicas por debajo de los nanosegundos.

Tenga en cuenta que esto solo funcionará para medir el código que tarda (muy) poco tiempo en ejecutarse (es decir, unos pocos ciclos de reloj hasta unos pocos miles): si se ejecutan tanto tiempo que es probable que sean interrumpidos por alguna interrupción -heh- , entonces claramente no es posible dar un resultado reproducible y preciso; La consecuencia es que la medición nunca termina: es decir, continúa midiendo hasta que estadísticamente está 99.9% seguro de que tiene la respuesta correcta que nunca sucede en una máquina que tiene otros procesos en ejecución cuando el código tarda demasiado.

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

Carlo Wood
fuente
0

Si desea ahorrar tiempo y líneas de código, puede hacer que la medición del tiempo de ejecución de la función sea una macro de una línea:

a) Implemente una clase de medición de tiempo como ya se sugirió anteriormente (aquí está mi implementación para Android):

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) Agregue una macro conveniente que use el nombre de la función actual como TAG (usar una macro aquí es importante, de lo contrario __FUNCTION__evaluará en MeasureExecutionTimelugar de la función que desea medir

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

c) Escriba su macro al comienzo de la función que desea medir. Ejemplo:

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

Lo que dará como resultado la siguiente salida:

ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms

Tenga en cuenta que esto (como todas las otras soluciones sugeridas) medirá el tiempo entre el momento en que se llamó a su función y cuando regresó, no necesariamente el momento en que su CPU estaba ejecutando la función. Sin embargo, si no le da al programador ningún cambio para suspender su código de ejecución llamando a sleep () o similar, no hay diferencia entre ellos.

Constantin Geier
fuente