Qué interesante y a la vez, complicado parece que es obtener la velocidad (frecuencia) a la que opera la CPU, y que bien quedaría que nuestros programas marcasen de manera sencilla este dato.
Existen métodos sencillos para realizar esta labor bajo Windows, pero bajo mi punto de vista es algo "cutre", como por ejemplo, leer la información del registro. A continuación explicaré como se puede hacer esto con pocas lineas, eso si, usando ensamblador directamente desde C. Una vez compilado, es posible llamar a la función desde cualquier programa, por ejemplo, uno hecho con Visual Basic.
El método que a continuación explicaré se basa en el uso de la instrucción ensamblador RDTSC. Esta instrucción básicamente lo que hace es consultar al microprocesador el número de ciclos transcurridos desde que se ha iniciado (Time Stamp Counter). Sin embargo, este sistema tiene algunos problemas como contador de alta resolución:
- Poco fiable en sistemas multi-core
- Problemas al hibernar (reinicio del time stamp)
Pero para nuestra finalidad, no es de mayor relevancia. La idea básica para obtener los ciclos de la cpu es la siguiente:
Mediante las funciones de Win32 QueryPerformanceCounter y QueryPerformanceFrequency. QueryPerformanceFrequency devuelve la cantidad que es capaz de sumar el contador de alta resolución en 1s. Esto lo usaremos para medir 1s con exactitud.
El código que muestro a continuación es el código de la función de la DLL que nos devuelve el resultado del cálculo. La librería la he compilado con Visual C++ 2005. Los modificadores extern "C" __declspec(dllexport) son necesarios para exportar la función y que puedan ser llamados desde otros lenguajes (ej. Visual Basic):
extern "C" __declspec(dllexport) float cpufreq(){
float cpu_mhz;
long long int cstart, cstop;
unsigned long long int tfreq, tctr, tstp;
//obtengo la frecuencia del contador (64 bits sin signo)
//QueryPerformanceFrequency devuelve la cantidad que el contador es capaz de incrementar en 1 s
QueryPerformanceFrequency((LARGE_INTEGER*)&tfreq);
if(tfreq!=0){
//obtengo el valor actual del contador de alta resolución
QueryPerformanceCounter((LARGE_INTEGER*)&tstp);
//sumo la frecuencia al valor del contador
tstp+=tfreq;
//bloque de codigo en ensamblador
__asm{
rdtsc //read stamp counter (EDX:EAX)
//transfiere los 32 bits superiores a EDX y los inferiores a EAX
mov dword ptr[cstart], eax //muevo los primeros 32 bits a los 32 bits de la variable
mov dword ptr[cstart+4], edx //los siguientes 32 bits (4 bytes) pasan a la parte superior
}
//ejecuta hasta que el contador supere al valor anterior + frecuencia resolucion
//(esto es, hasta que transcurra 1 s con mucha precisión
do{
QueryPerformanceCounter((LARGE_INTEGER*)&tctr);
}while(tctr<tstp);
//vuelve a obtener los ciclos
__asm{
rdtsc
mov dword ptr[cstop], eax
mov dword ptr[cstop+4], edx
}
//La velocidad simplemente será la diferencia (en hz) dividad entre 1.000.000 (Mhz)
cpu_mhz=((float)cstop-(float)cstart)/1000000;
return cpu_mhz;
}else{
return 0;
}
A continuación incluyo el código fuente de la librería (proyecto Visual C++ .NET 2005) junto con la DLL compilada. Para utilizarla desde C, es necesario cargarla mediante LoadLibrary. Para usarla en Visual Basic, la cabecera de importación de la función es la siguiente:
Public Declare Function cpufreq Lib "SpeedLib.dll" () As Single
Nótese que se debe tener la librería SpeedLib.dll en la ruta de trabajo, o sino, sustituír "SpeedLib.dll" por la ruta completa de la librería. Un código de ejemplo para probar la librería puede ser el siguiente:
Visual Basic .NET
Module Module1
Private Declare Function cpufreq Lib "SpeedLib.dll" () As Single
Sub Main()
Console.WriteLine("Velocidad " & cpufreq() & " mhz")
Console.ReadKey()
End Sub
End Module
Visual Basic 6
Private Declare Function cpufreq Lib "SpeedLib.dll" () As Single
Sub Main()
Msgbox("Velocidad " & cpufreq() & " mhz")
End Sub
Descargar código fuente y librería compilada