Calcular la velocidad de la CPU con C/C++ y crear DLL

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

download.jpg
page_revision: 17, last_edited: 1201908175|%e %b %Y, %H:%M %Z (%O ago)