timer - tiempo - Algoritmo de medición de la frecuencia de la CPU
rendimiento arquitectura de computadoras (9)
¿Cuáles son los algoritmos comunes que se utilizan para medir la frecuencia del procesador?
Esa era la intención de cosas como BogoMIPS , pero las CPU son mucho más complicadas hoy en día. Las CPU superescalares pueden emitir múltiples instrucciones por reloj, haciendo que cualquier medida basada en el conteo de ciclos de reloj ejecute un bloque de instrucciones altamente inexacto.
Las frecuencias de CPU también son variables en función de la carga y / o temperatura ofrecidas. El hecho de que la CPU se esté ejecutando actualmente a 800 MHz no significa que siempre estará funcionando a 800 MHz, sino que podría acelerarse hacia arriba o hacia abajo según sea necesario.
Si realmente necesita saber la frecuencia del reloj, se debe pasar como un parámetro. Una EEPROM en la placa suministraría la frecuencia base, y si el reloj puede variar, necesitaría poder leer los registros de estado de alimentación de la CPU (o hacer una llamada al sistema operativo) para averiguar la frecuencia en ese instante.
Con todo lo dicho, puede haber otras formas de lograr lo que estás tratando de hacer. Por ejemplo, si quiere hacer mediciones de alta precisión de cuánto tiempo tarda una determinada ruta de codificación, es probable que la CPU tenga contadores de rendimiento que funcionen a una frecuencia fija que son una mejor medida del tiempo de la pared que leer un registro de conteo de marcas.
No estoy seguro de por qué necesita ensamblaje para esto. Si estás en una máquina que tiene el sistema de archivos / proc, ejecuta:
> cat /proc/cpuinfo
podría darte lo que necesitas
Voy a salir con varios detalles en esta respuesta, pero qué diablos ...
Tuve que abordar este problema hace años en PC basadas en Windows, por lo que lidiaba con procesadores de la serie Intel x86 como 486, Pentium, etc. El algoritmo estándar en esa situación era hacer una larga serie de instrucciones DIVide, porque esas son, por lo general, las instrucciones más vinculadas a la CPU en el conjunto de Intel. Por lo tanto, la captación previa de memoria y otros problemas arquitectónicos no afectan materialmente el tiempo de ejecución de la instrucción: la cola de captación previa siempre está llena y la instrucción en sí no toca ninguna otra memoria.
Puede medir el tiempo utilizando el reloj de mayor resolución al que podría acceder en el entorno en el que se está ejecutando (en mi caso, estaba funcionando cerca del arranque en un PC compatible, así que estaba programando directamente los chips del temporizador en la placa base). recomendado en un sistema operativo real, por lo general hay alguna API adecuada para llamar en estos días).
El principal problema con el que tienes que lidiar es con diferentes tipos de CPU. En ese momento, Intel, AMD y algunos proveedores más pequeños como Cyrix fabricaban procesadores x86. Cada modelo tenía sus propias características de rendimiento en comparación con la instrucción DIV. Mi función de sincronización de montaje simplemente devolvería una cantidad de ciclos de reloj tomados por una cierta cantidad fija de instrucciones DIV hechas en un ciclo cerrado.
Entonces, lo que hice fue recopilar algunos tiempos (valores de retorno sin procesar de esa función) de las computadoras reales que ejecutaban cada modelo de procesador que deseaba cronometrar, y registrarlos en una hoja de cálculo comparándolos con la velocidad del procesador y el tipo de procesador conocidos. De hecho, tenía una herramienta de línea de comandos que era solo un delgado caparazón alrededor de mi función de sincronización, y me gustaría llevar un disco a las tiendas de informática y obtener los tiempos de los modelos de visualización. (Trabajé para una compañía muy pequeña en ese momento).
Usando esos tiempos crudos, podría trazar un gráfico teórico de los tiempos que debería obtener para cualquier velocidad conocida de esa CPU en particular.
Aquí estaba el truco: siempre odié cuando ejecutabas una utilidad y anunciaba que tu CPU era de 99.8 Mhz o lo que sea. Claramente, era de 100 Mhz y solo había un pequeño error de redondeo en la medición. En mi hoja de cálculo grabé las velocidades reales que vendía cada proveedor de procesadores. Luego utilizaría la trama de los tiempos reales para estimar los tiempos proyectados para cualquier velocidad conocida. Pero construiría una tabla de puntos a lo largo de la línea donde los tiempos deberían redondear a la siguiente velocidad.
En otras palabras, si 100 tics para hacer toda esa división repetitiva significaban 500 Mhz, y 200 tics significaban 250 Mhz, entonces construiría una tabla que dijera que cualquier valor inferior a 150 era de 500 Mhz, y cualquier valor superior a 250 Mhz. (Suponiendo que esas fueran las únicas dos velocidades disponibles de ese proveedor de chips). Fue agradable, porque incluso si alguna pieza extraña de software en la PC estuviese interrumpiendo mis tiempos, el resultado final a menudo seguiría sin funcionar.
Por supuesto ahora, en estos días de overclocking, velocidades de reloj dinámicas para la administración de energía, y otros trucos como ese, este esquema sería mucho menos práctico. Por lo menos, necesitaría hacer algo para asegurarse de que la CPU estaba en su velocidad más alta elegida dinámicamente antes de ejecutar su función de temporización.
De acuerdo, volveré a espantar a los niños de mi jardín ahora.
"lmbench" proporciona un algoritmo de frecuencia de CPU portátil para diferentes arquitecturas.
Ejecuta algunos bucles diferentes y la velocidad de reloj del procesador es el mayor divisor común de las frecuencias de ejecución de los diversos bucles.
este método siempre debería funcionar cuando podemos obtener bucles con recuentos de ciclos que son relativamente primos.
Una forma en las CPU Intel x86, ya que Pentium sería usar dos muestreos de la instrucción RDTSC con un ciclo de retardo de tiempo de pared conocido, por ejemplo:
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
uint64_t rdtsc(void) {
uint64_t result;
__asm__ __volatile__ ("rdtsc" : "=A" (result));
return result;
}
int main(void) {
uint64_t ts0, ts1;
ts0 = rdtsc();
sleep(1);
ts1 = rdtsc();
printf("clock frequency = %llu/n", ts1 - ts0);
return 0;
}
(en plataformas de 32 bits con GCC)
RDTSC está disponible en el anillo 3 si se establece el indicador TSC en CR4, que es común pero no está garantizado. Una deficiencia de este método es que es vulnerable a los cambios de escala de frecuencia que afectan el resultado si ocurren dentro del retraso. Para mitigarlo, podría ejecutar código que mantenga ocupada la CPU y consultar constantemente la hora del sistema para ver si su período de retraso ha expirado, para mantener la CPU en el estado de frecuencia más alta disponible.
Uso el siguiente algoritmo (pseudo):
basetime=time(); /* time returns seconds */
while (time()==basetime);
stclk=rdtsc(); /* rdtsc is an assembly instruction */
basetime=time();
while (time()==basetime
endclk=rdtsc();
nclks=encdclk-stclk;
En este punto, puede suponer que ha determinado la frecuencia del reloj, pero a pesar de que parece correcto, puede mejorarse.
Todas las PC contienen un dispositivo PIT (temporizador de intervalo programable) que contiene contadores que se utilizan (solían usarse) para puertos serie y el reloj del sistema. Fue alimentado con una frecuencia de 1193182 Hz. El contador del reloj del sistema se estableció en el valor de cuenta regresiva más alto (65536), lo que da como resultado una frecuencia de marcación del reloj del sistema de 1193182/65536 => 18.2065 Hz o una vez cada 54.925 milisegundos.
Por lo tanto, dependerá del número de ticks necesarios para que el reloj incremente al siguiente segundo. Por lo general, se requieren 18 ticks y, a veces, 19. Esto se puede manejar ejecutando el algoritmo (arriba) dos veces y almacenando los resultados. Los dos resultados serán equivalentes a dos secuencias de 18 ticks o una 18 y una 19. No se producirán dos 19 seguidos. Entonces, al tomar el menor de los dos resultados, tendrá un segundo punto de 18 ticks. Ajuste este resultado multiplicándolo por 18.2065 y dividiendo por 18.0 o, usando la aritmética de números enteros, multiplique por 182065, agregue 90000 y divida por 180000. 90000 es la mitad de 180000 y está ahí para redondear. Si elige el cálculo con una ruta entera, asegúrese de estar usando multiplicación y división de 64 bits.
Ahora tendrá una velocidad de reloj de la CPU x en Hz que se puede convertir a kHz ((x + 500) / 1000) o MHz ((x + 5000000) / 1000000). Los 500 y 500000 son la mitad de 1000 y 1000000 respectivamente y están ahí para redondear. Para calcular MHz, no pase por el valor kHz porque pueden surgir problemas de redondeo. Use el valor de Hz y el segundo algoritmo.
Una opción es detectar la frecuencia de la CPU, ejecutando el código con instrucciones conocidas por ciclo
Esta funcionalidad está contenida en 7zip, desde aproximadamente v9.20, creo.
> 7z b
7-Zip 9.38 beta Copyright (c) 1999-2014 Igor Pavlov 2015-01-03
CPU Freq: 4266 4000 4266 4000 2723 4129 3261 3644 3362
El número final está destinado a ser correcto (y en mi PC y muchos otros, he encontrado que es bastante correcto; la prueba se ejecuta muy rápido para que el turbo no funcione, y los servidores configurados en modos Equilibrado / Ahorro de energía muy probablemente dan lecturas de alrededor de 1ghz)
El código fuente está en GitHub (fuente oficial es una descarga de 7-zip.org)
Con la porción más significativa es:
#define YY1 sum += val; sum ^= val;
#define YY3 YY1 YY1 YY1 YY1
#define YY5 YY3 YY3 YY3 YY3
#define YY7 YY5 YY5 YY5 YY5
static const UInt32 kNumFreqCommands = 128;
EXTERN_C_BEGIN
static UInt32 CountCpuFreq(UInt32 sum, UInt32 num, UInt32 val)
{
for (UInt32 i = 0; i < num; i++)
{
YY7
}
return sum;
}
EXTERN_C_END
Las CPU Intel después de que Core Duo admitan dos registros específicos del modelo llamados IA32_MPERF y IA32_APERF.
MPERF cuenta a la frecuencia máxima que admite la CPU, mientras que APERF cuenta a la frecuencia actual real.
La frecuencia real viene dada por:
Puedes leerlos con este flujo
; read MPERF
mov ecx, 0xe7
rdmsr
mov mperf_var_lo, eax
mov mperf_var_hi, edx
; read APERF
mov ecx, 0xe8
rdmsr
mov aperf_var_lo, eax
mov aperf_var_hi, edx
pero tenga en cuenta que rdmsr es una instrucción privilegiada y solo se puede ejecutar en el anillo 0.
No sé si el sistema operativo proporciona una interfaz para leer estos, aunque su uso principal es para la administración de energía, por lo que podría no proporcionar dicha interfaz.