Tiempo de ejecución del programa C

209

Tengo un programa en C que pretende ejecutarse en paralelo en varios procesadores. Necesito poder registrar el tiempo de ejecución (que puede ser de 1 segundo a varios minutos). He buscado respuestas, pero todas parecen sugerir el uso de la clock()función, que luego implica calcular el número de relojes que tomó el programa dividido por el Clocks_per_secondvalor.

¿No estoy seguro de cómo Clocks_per_secondse calcula el valor?

En Java, solo tomo el tiempo actual en milisegundos antes y después de la ejecución.

¿Hay algo similar en C? He echado un vistazo, pero parece que no puedo encontrar una manera de obtener algo mejor que una segunda resolución.

También sé que un perfilador sería una opción, pero estoy buscando implementar un temporizador yo mismo.

Gracias

Roger
fuente
3
¿Qué marcos de OS / API está utilizando / disponible? ¿Simplemente C?
typo.pl
44
Es un programa bastante pequeño, simplemente C
Roger
He escrito en detalles sobre la implementación de una solución portátil en esta respuesta: stackoverflow.com/questions/361363/…
Alexander Saprykin
tiempo necesario para ejecutar una función completa stackoverflow.com/a/40380118/6180077
Abdullah Farweez

Respuestas:

344

CLOCKS_PER_SECes una constante que se declara en <time.h>. Para obtener el tiempo de CPU utilizado por una tarea dentro de una aplicación C, use:

clock_t begin = clock();

/* here, do your time-consuming job */

clock_t end = clock();
double time_spent = (double)(end - begin) / CLOCKS_PER_SEC;

Tenga en cuenta que esto devuelve el tiempo como un tipo de punto flotante. Esto puede ser más preciso que un segundo (por ejemplo, mide 4.52 segundos). La precisión depende de la arquitectura; en sistemas modernos, obtiene fácilmente 10 ms o menos, pero en máquinas Windows más antiguas (de la era Win98) estaba más cerca de 60 ms.

clock()es el estándar C; funciona "en todas partes". Hay funciones específicas del sistema, comogetrusage() en sistemas tipo Unix.

Java System.currentTimeMillis()no mide lo mismo. Es un "reloj de pared": puede ayudarlo a medir cuánto tiempo le llevó ejecutar el programa, pero no le dice cuánto tiempo de CPU se utilizó. En un sistema multitarea (es decir, todos ellos), estos pueden ser muy diferentes.

Thomas Pornin
fuente
1
Me da un resultado muy aleatorio: obtengo una mezcla de números grandes / pequeños / negativos sobre el mismo fragmento de código. GCC 4.7 Linux 3.2 AMD64
3
Sí: clock()devuelve un tiempo en una escala interna llamada "relojes", y CLOCKS_PER_SECes el número de relojes por segundo, por lo que dividir por CLOCKS_PER_SECrendimientos un tiempo en segundos. En el código anterior, el valor es a doublepara que pueda escalarlo a voluntad.
Thomas Pornin
18
Gran advertencia: clock () devuelve la cantidad de tiempo que el sistema operativo ha pasado ejecutando su proceso, y no la cantidad real de tiempo transcurrido. Sin embargo, esto está bien para sincronizar un bloque de código, pero no para medir el tiempo transcurrido en el mundo real.
2
Dijo que quiere medir un programa multiproceso. No estoy seguro de que un reloj () sea adecuado para esto, porque resume los tiempos de ejecución de todos los subprocesos, por lo que el resultado se verá como si el código se ejecutara secuencialmente. Para tales cosas, uso omp_get_wtime (), pero por supuesto necesito asegurarme de que el sistema no esté ocupado con otros procesos.
Youda008
1
Debo mencionar algunas cosas a pesar de que este hilo era más relevante hace un año: CLOCKS_PER_SECes un long intvalor 1000000, que da tiempo en microsegundos cuando no está dividido; no ciclos de reloj de la CPU. Por lo tanto, no es necesario tener en cuenta la frecuencia dinámica, ya que el reloj aquí está en microsegundos (¿tal vez ciclos de reloj para una CPU de 1 MHz?) Hice un breve programa en C imprimiendo ese valor y fue 1000000 en mi computadora portátil i7-2640M, con frecuencia dinámica que permite de 800 MHz a 2.8 GHz, incluso usando Turbo Boost para llegar hasta 3.5 GHz.
DDPWNAGE
111

Si está utilizando el shell de Unix para ejecutar, puede usar el comando time.

haciendo

$ time ./a.out

suponiendo a.out como el ejecutable le dará el tiempo necesario para ejecutar esto

S..K
fuente
3
@acgtyrant pero sólo para programas sencillos, debido a que va a tomar todo el tiempo del programa, incluyendo la entrada, salida, etc.
phuclv
1
Si está en Linux y ha reducido su (micro) punto de referencia a un programa con una sobrecarga de inicio insignificante, por ejemplo, un ejecutable estático que ejecuta su bucle activo durante unos segundos, puede usarlo perf stat ./a.outpara obtener contadores de rendimiento de hardware para errores de caché y predicciones erróneas de sucursales, e IPC.
Peter Cordes
61

En vainilla simple C:

#include <time.h>
#include <stdio.h>

int main()
{
    clock_t tic = clock();

    my_expensive_function_which_can_spawn_threads();

    clock_t toc = clock();

    printf("Elapsed: %f seconds\n", (double)(toc - tic) / CLOCKS_PER_SEC);

    return 0;
}
Alexandre C.
fuente
66
Los mejores nombres de variables que he visto en mucho tiempo. tic = "hora en reloj", toc = "hora de espera". Pero también tic-toc = "tick-tock". Así es como estoy etiquetando el tiempo de aquí en adelante.
Logan Schelly
60

Funcionalmente quieres esto:

#include <sys/time.h>

struct timeval  tv1, tv2;
gettimeofday(&tv1, NULL);
/* stuff to do! */
gettimeofday(&tv2, NULL);

printf ("Total time = %f seconds\n",
         (double) (tv2.tv_usec - tv1.tv_usec) / 1000000 +
         (double) (tv2.tv_sec - tv1.tv_sec));

Tenga en cuenta que esto mide en microsegundos, no solo segundos.

Wes Hardaker
fuente
2
El compilador MinGW está basado en GCC. Entonces funcionará en ello. Pero si usa el compilador visual C, obtendrá un error.
user2550754
11
Sí, funcionará en Windows con una biblioteca de CA que admite la llamada gettimeofday. En realidad no importa cuál sea el compilador, solo tiene que vincularlo con una biblioteca libc decente. Que, en el caso de mingw, no es el predeterminado de Windows.
Wes Hardaker
1
Esto funciona para mí en Windows XP con cygwin gcc y Linux Ubuntu. Esto es justo lo que quería.
Amor y paz - Joe Codeswell
gettimeofdayes obsoleto y no se recomienda para código nuevo. Su página de manual POSIX recomienda clock_gettime en su lugar, lo que le permite pedir CLOCK_MONOTONICque no se vea afectado por los cambios en el reloj del sistema y, por lo tanto, es mejor como un intervalo de tiempo. (Ver la respuesta de JohnSll ). En los sistemas Linux modernos, por ejemplo, gettimeofday es básicamente un contenedor para clock_gettime que convierte nanosegundos a microsegundos.
Peter Cordes
12

La mayoría de los programas simples tienen tiempo de cálculo en milisegundos. Entonces, supongo, esto lo encontrará útil.

#include <time.h>
#include <stdio.h>

int main(){
    clock_t start = clock();
    // Execuatable code
    clock_t stop = clock();
    double elapsed = (double)(stop - start) * 1000.0 / CLOCKS_PER_SEC;
    printf("Time elapsed in ms: %f", elapsed);
}

Si desea calcular el tiempo de ejecución de todo el programa y está en un sistema Unix, ejecute su programa usando el comando de tiempo como estetime ./a.out

adimoh
fuente
En Windows, al menos, el factor es al menos 100 pero no 1000 y no es exacto
boctulus
66
Esta respuesta no agrega nada que no estaba en la respuesta de Alexandre C de dos años antes.
Jonathan Leffler
3
@boctulus: 1s siempre es 1000ms, también en Windows.
alk
9

Se han sugerido muchas respuestas clock()y luego CLOCKS_PER_SECde time.h. Probablemente sea una mala idea, porque esto es lo /bits/time.hque dice mi archivo:

/* ISO/IEC 9899:1990 7.12.1: <time.h>
The macro `CLOCKS_PER_SEC' is the number per second of the value
returned by the `clock' function. */
/* CAE XSH, Issue 4, Version 2: <time.h>
The value of CLOCKS_PER_SEC is required to be 1 million on all
XSI-conformant systems. */
#  define CLOCKS_PER_SEC  1000000l

#  if !defined __STRICT_ANSI__ && !defined __USE_XOPEN2K
/* Even though CLOCKS_PER_SEC has such a strange value CLK_TCK
presents the real value for clock ticks per second for the system.  */
#   include <bits/types.h>
extern long int __sysconf (int);
#   define CLK_TCK ((__clock_t) __sysconf (2))  /* 2 is _SC_CLK_TCK */
#  endif

Por CLOCKS_PER_SEClo tanto, podría definirse como 1000000, dependiendo de las opciones que use para compilar, por lo que no parece una buena solución.

Stephen
fuente
1
Gracias por la información, pero ¿hay alguna alternativa mejor todavía?
ozanmuyes
44
Este no es un problema práctico: sí, los sistemas Posix siempre lo han hecho CLOCK_PER_SEC==1000000, pero al mismo tiempo, todos usan precisión de 1 µs para su implementación de reloj (); Por cierto, tiene la buena propiedad para reducir los problemas de intercambio. Si desea medir eventos potencialmente muy rápidos, digamos por debajo de 1 ms, primero debe preocuparse por la precisión (o resolución) de la función clock (), que es necesariamente más gruesa que 1 µs en Posix, pero a menudo también es mucho más gruesa; la solución habitual es ejecutar la prueba muchas veces; Sin embargo, la pregunta formulada no parecía requerirla.
AntoineL
¿Por qué no sería una buena solución? Obtiene algún valor de clock(), si divide ese valor con CLOCK_PER_SECusted, se garantiza que obtendrá el tiempo en segundos que tomó la CPU. La responsabilidad de medir la velocidad real del reloj es responsabilidad de la clock()función, no suya.
Zaffy
9

La respuesta de Thomas Pornin como macros:

#define TICK(X) clock_t X = clock()
#define TOCK(X) printf("time %s: %g sec.\n", (#X), (double)(clock() - (X)) / CLOCKS_PER_SEC)

Úselo así:

TICK(TIME_A);
functionA();
TOCK(TIME_A);

TICK(TIME_B);
functionB();
TOCK(TIME_B);

Salida:

time TIME_A: 0.001652 sec.
time TIME_B: 0.004028 sec.
hklel
fuente
4

Debe tener en cuenta que medir el tiempo que tardó un programa en ejecutarse depende mucho de la carga que tenga la máquina en ese momento específico.

Sabiendo que, la forma de obtener el tiempo actual en C se puede lograr de diferentes maneras, una más fácil es:

#include <time.h>

#define CPU_TIME (getrusage(RUSAGE_SELF,&ruse), ruse.ru_utime.tv_sec + \
  ruse.ru_stime.tv_sec + 1e-6 * \
  (ruse.ru_utime.tv_usec + ruse.ru_stime.tv_usec))

int main(void) {
    time_t start, end;
    double first, second;

    // Save user and CPU start time
    time(&start);
    first = CPU_TIME;

    // Perform operations
    ...

    // Save end time
    time(&end);
    second = CPU_TIME;

    printf("cpu  : %.2f secs\n", second - first); 
    printf("user : %d secs\n", (int)(end - start));
}

Espero eso ayude.

¡Saludos!

redent84
fuente
4

(Faltan todas las respuestas aquí, si su administrador del sistema cambia la hora del sistema, o si su zona horaria tiene diferentes horarios de invierno y verano. Por lo tanto ...)

Sobre el uso de Linux: clock_gettime(CLOCK_MONOTONIC_RAW, &time_variable); no se ve afectado si el administrador del sistema cambia la hora, o si vives en un país con un horario de invierno diferente del horario de verano, etc.

#include <stdio.h>
#include <time.h>

#include <unistd.h> /* for sleep() */

int main() {
    struct timespec begin, end;
    clock_gettime(CLOCK_MONOTONIC_RAW, &begin);

    sleep(1);      // waste some time

    clock_gettime(CLOCK_MONOTONIC_RAW, &end);

    printf ("Total time = %f seconds\n",
            (end.tv_nsec - begin.tv_nsec) / 1000000000.0 +
            (end.tv_sec  - begin.tv_sec));

}

man clock_gettime estados:

CLOCK_MONOTONIC
              Clock  that  cannot  be set and represents monotonic time since some unspecified starting point.  This clock is not affected by discontinuous jumps in the system time
              (e.g., if the system administrator manually changes the clock), but is affected by the incremental adjustments performed by adjtime(3) and NTP.
JohnSll
fuente
¿Puedes explicar el cálculo que usaste para obtener el número de segundos? No es obvio lo que está pasando.
Colin Keenan
1
¿No (end.tv_nsec - begin.tv_nsec) / 1000000000.0resultaría esto 0siempre?
alk
@alk: no, dividiendo por unos doubledisparadores literales INT o longpara doubleconversión antes de la división. Por supuesto, podría limitarse a un número entero e imprimir la tv_secparte y luego la parte fraccionaria con cero %ld.%09ld, pero convertir a doble es fácil y 53 bits de precisión suelen ser suficientes para los tiempos de referencia.
Peter Cordes
1
(Vaya, la sustracción de la parte de nanosegundos puede necesitar llevarse a la parte de segundos, por lo que usar el doble y dejar que sea negativo evita ese problema. Para usar una cadena de formato entero puro, necesitaría algo timespec_subtractcomo lo timeval_subtractsugerido en el manual de glibc : gnu.org/software/libc/manual/html_node/Elapsed-Time.html )
Peter Cordes
3

ANSI C solo especifica funciones de tiempo de segunda precisión. Sin embargo, si está ejecutando en un entorno POSIX, puede usar gettimeofday () que proporciona una resolución de microsegundos del tiempo transcurrido desde la época de UNIX.

Como nota al margen, no recomendaría usar clock () ya que está mal implementado en muchos sistemas (¿si no todos?) Y no es preciso, además del hecho de que solo se refiere a cuánto tiempo ha pasado su programa en la CPU y no la duración total del programa, que según su pregunta es lo que supongo que le gustaría medir.

Shinnok
fuente
El estándar ISO C (suponiendo que esto es lo que significa ANSI C ) no especifica a propósito la precisión de las funciones de tiempo . Luego, específicamente en una implementación POSIX, o en Windows, la precisión de las funciones del reloj de pared (ver la respuesta de Thomas) es en segundos. Pero la precisión del reloj () suele ser mayor, y siempre 1 µs en Posix (independientemente de la precisión).
AntoineL
2

Todas las soluciones no funcionan en mi sistema.

Puedo empezar a usar

#include <time.h>

double difftime(time_t time1, time_t time0);
Ankit Chilamatur
fuente
2
Esto da la diferencia entre dos time_tvalores como un doble. Como los time_tvalores solo son precisos por segundo, tiene un valor limitado para imprimir el tiempo que tardan los programas de ejecución corta, aunque puede ser útil para los programas que se ejecutan durante largos períodos.
Jonathan Leffler
Por alguna razón, pasar un par de clock_ts difftimeparece funcionar para mí con la precisión de una centésima de segundo. Esto está en Linux x86. Tampoco puedo obtener la resta de stopy startpara trabajar.
ragerdl
@ragerdl: debe pasar a difftime() clock() / CLOCKS_PER_SEC, ya que espera segundos.
alk
2
    #include<time.h>
    #include<stdio.h>
    int main(){
clock_t begin=clock();

    int i;
for(i=0;i<100000;i++){
printf("%d",i);

}
clock_t end=clock();
printf("Time taken:%lf",(double)(end-begin)/CLOCKS_PER_SEC);
}

Este programa funcionará a las mil maravillas.

Ravi Kumar Yadav
fuente
2

He encontrado que el reloj habitual (), todos lo recomiendan aquí, por alguna razón se desvía enormemente de una ejecución a otra, incluso para el código estático sin efectos secundarios, como dibujar en la pantalla o leer archivos. Podría ser porque la CPU cambia los modos de consumo de energía, el sistema operativo le da diferentes prioridades, etc.

Por lo tanto, la única forma de obtener el mismo resultado de manera confiable cada vez con clock () es ejecutar el código medido en un bucle varias veces (durante varios minutos), tomando precauciones para evitar que el compilador lo optimice: los compiladores modernos pueden calcular previamente el código sin los efectos secundarios que se ejecutan en un bucle, y moverlo fuera del bucle., por ejemplo, usando una entrada aleatoria para cada iteración.

Después de recolectar suficientes muestras en una matriz, se ordena esa matriz y se toma el elemento central, llamado mediana. La mediana es mejor que la media, ya que arroja desviaciones extremas, como decir que el antivirus ocupa toda la CPU o el sistema operativo haciendo alguna actualización.

Aquí hay una sencilla utilidad para medir el rendimiento de ejecución del código C / C ++, promediando los valores cercanos a la mediana: https://github.com/saniv/gauge

Todavía estoy buscando una forma más robusta y rápida de medir el código. Probablemente se podría intentar ejecutar el código en condiciones controladas en el metal desnudo sin ningún sistema operativo, pero eso dará un resultado poco realista, porque en realidad el sistema operativo se involucra.

x86 tiene estos contadores de rendimiento de hardware, que incluyen el número real de instrucciones ejecutadas, pero son difíciles de acceder sin ayuda del sistema operativo, difíciles de interpretar y tienen sus propios problemas ( http://archive.gamedev.net/archive/reference/articles /article213.html ). Aún así, podrían ser útiles para investigar la naturaleza del cuello de la botella (acceso a datos o cálculos reales sobre esos datos).

SmugLispWeenie
fuente
Sí, las CPU modernas x86 están inactivas mucho más lento que el turbo máximo. Dependiendo de la configuración del "gobernador", la aceleración hasta la velocidad máxima del reloj puede tardar un milisegundo (Skylake con gestión de estado P del hardware, especialmente con energy_performance_preference establecido en performance) o muchas decenas de milisegundos. en.wikipedia.org/wiki/Dynamic_frequency_scaling . Y sí, el rendimiento medio suele ser una buena opción; el extremo superior generalmente tiene algunos picos de interferencia.
Peter Cordes
A menudo, su mejor opción para evitar que el trabajo se optimice es una entrada de línea de comandos y devolver el resultado. O escriba una función en un archivo separado del mainque toma un argumento y devuelve un resultado, y no use la optimización del tiempo de enlace. Entonces el compilador no puede alinearlo con la persona que llama. Solo funciona si la función ya incluye algún tipo de bucle; de ​​lo contrario, la sobrecarga de llamadas / ret es demasiado alta.
Peter Cordes
El compilador aún puede optimizar la entrada de línea de comando única fuera del bucle, si la procesa con código estático sin efectos secundarios. Por lo tanto, es mejor generar una entrada aleatoria para cada iteración. Obviamente, rand () debería llamarse fuera del código medido, antes del primer reloj (), porque rand () también podría resultar en una llamada al sistema, muestreando un generador de entropía de hardware (que en sistemas más antiguos era el movimiento del mouse). Simplemente no olvide imprimir cada bit de la salida, de lo contrario, el compilador puede decidir que no necesita toda la salida como parte de ella. Eso se puede hacer con decir CRC32.
SmugLispWeenie
Si su código bajo prueba está en un archivo separado y no utiliza la optimización del tiempo de enlace, no hay forma de que el compilador pueda hacer CSE para optimizar entre llamadas. La persona que llama no puede asumir nada acerca de que la persona que llama no tiene efectos secundarios visibles. Esto le permite poner algo relativamente corto dentro de un ciclo de repetición para que sea lo suficientemente largo como para cronometrar, con solo una llamada / ret sobrecarga. Si lo deja en línea, debe verificar el asm generado para asegurarse de que no haya levantado un cálculo de un bucle como usted dice.
Peter Cordes
La forma específica del compilador es usar (por ejemplo) GNU C inline asm para forzar a un compilador a materializar un resultado en un registro, y / u olvidar lo que sabe sobre el valor de una variable, sin introducir instrucciones adicionales. El equivalente de "Escape" y "Clobber" en los enlaces de MSVC a un video sobre creación de perfiles y microbenchmarking (charla CppCon 2015 del desarrollador de clang Chandler Carruth) No hay un equivalente de MSVC, pero la pregunta en sí muestra las funciones de GNU C y cómo usarlas.
Peter Cordes
0

Algunos podrían encontrar útil un tipo diferente de información: me dieron este método de medición del tiempo como parte de un curso universitario sobre programación GPGPU con NVidia CUDA ( descripción del curso ). Combina métodos vistos en publicaciones anteriores, y simplemente lo publico porque los requisitos le dan credibilidad:

unsigned long int elapsed;
struct timeval t_start, t_end, t_diff;
gettimeofday(&t_start, NULL);

// perform computations ...

gettimeofday(&t_end, NULL);
timeval_subtract(&t_diff, &t_end, &t_start);
elapsed = (t_diff.tv_sec*1e6 + t_diff.tv_usec);
printf("GPU version runs in: %lu microsecs\n", elapsed);

Supongo que podría multiplicarse con, por ejemplo, 1.0 / 1000.0para obtener la unidad de medida que se adapte a sus necesidades.

ワ イ き ん ぐ
fuente
1
gettimeofday es obsoleto y no se recomienda. En clock_gettimecambio, su página de manual POSIX recomienda , lo que le permite pedir CLOCK_MONOTONICque no se vea afectado por los cambios en el reloj del sistema, y ​​por lo tanto, es mejor como un temporizador de intervalo. En los sistemas Linux modernos, por ejemplo, gettimeofdayes básicamente un contenedor clock_gettimeque convierte nanosegundos a microsegundos. (Ver la respuesta de JohnSll).
Peter Cordes
Este método fue agregado por @Wes Hardaker, la principal diferencia está usando timeval_subtract.
ワ イ き ん ぐ
Ok, entonces la única parte útil de su respuesta es el nombre de una función que no define, y que no está en la biblioteca estándar. (Solo en el manual de glibc: gnu.org/software/libc/manual/html_node/Elapsed-Time.html ).
Peter Cordes
-2

Comparación del tiempo de ejecución del ordenamiento de burbujas y el ordenamiento de selección Tengo un programa que compara el tiempo de ejecución del ordenamiento de burbujas y el ordenamiento de selección. Para averiguar el tiempo de ejecución de un bloque de código, calcule el tiempo antes y después del bloque por

 clock_t start=clock();
 
 clock_t end=clock();
 CLOCKS_PER_SEC is constant in time.h library

Código de ejemplo:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
   int a[10000],i,j,min,temp;
   for(i=0;i<10000;i++)
   {
      a[i]=rand()%10000;
   }
   //The bubble Sort
   clock_t start,end;
   start=clock();
   for(i=0;i<10000;i++)
   {
     for(j=i+1;j<10000;j++)
     {
       if(a[i]>a[j])
       {
         int temp=a[i];
         a[i]=a[j];
         a[j]=temp;
       }
     }
   }
   end=clock();
   double extime=(double) (end-start)/CLOCKS_PER_SEC;
   printf("\n\tExecution time for the bubble sort is %f seconds\n ",extime);

   for(i=0;i<10000;i++)
   {
     a[i]=rand()%10000;
   }
   clock_t start1,end1;
   start1=clock();
   // The Selection Sort
   for(i=0;i<10000;i++)
   {
     min=i;
     for(j=i+1;j<10000;j++)
     {
       if(a[min]>a[j])
       {
         min=j;
       }
     }
     temp=a[min];
     a[min]=a[i];
     a[i]=temp;
   }
   end1=clock();
   double extime1=(double) (end1-start1)/CLOCKS_PER_SEC;
   printf("\n");
   printf("\tExecution time for the selection sort is %f seconds\n\n", extime1);
   if(extime1<extime)
     printf("\tSelection sort is faster than Bubble sort by %f seconds\n\n", extime - extime1);
   else if(extime1>extime)
     printf("\tBubble sort is faster than Selection sort by %f seconds\n\n", extime1 - extime);
   else
     printf("\tBoth algorithms have the same execution time\n\n");
}
DUDATO JAGOSO
fuente
44
Esto realmente no agrega nada nuevo en comparación con la respuesta de adimoh , excepto que completa el bloque 'el código ejecutable' (o dos de ellos) con algún código real. Y esa respuesta no agrega nada que no estaba en la respuesta de Alexandre C de dos años antes.
Jonathan Leffler