Microsoft MVP

Email y Rss

email rss

Klout

Seguidores en facebook

Timeline de mi Twitter

Tienes preguntas?

Ideas de un Conejo
Más allá de los sistemas de información: (C#)=> videojuegos, soluciones a problemas interesantes y Sistemas Operativos
XNA
C#
Eventos
Sistemas Operativos
Review
Varios
PL/SQL
Acerca de

C# – Cuando la precisión que da el StopWatch no es suficiente…

February 2nd, 2010 by JuanK

TweetFollow @JuanKRuiz

Share

Medir tiempo en nanosegundos

Hola!

He observado que es muy frecuente cuando alguien quiere hacer una prueba de rendimiento (sobre todo a nivel académico) que la resolución que da el objeto StopWatch (System.Diagnostics.Stopwatch) de Milisegundos resulta no ser siempre suficiente.

En esos casos lo mejor es recurrir a las funciones de la API para crear algo más de acuerdo a nuestras necesidades, de tal forma que podamos medir el tiempo transcurrido con una precisión mayor a la que da  – por alguna razón – el objeto StopWatch, así que creare algo sencillo que permitirá lograr la precisión deseada, pero primero – como siempre –algo de teoría al respecto.

 

CÓMO CALCULAR EL TIEMPO

Para calcular el tiempo dentro de un computador debemos valernos de la información que nos brinda el procesador, como todos sabemos el procesador posee un atributo llamado frecuencia, el cual nos indica cuantos ciclos de reloj realiza el procesador cada segundo. De esta forma encontramos que hay procesadores 1 Ghz (un millon de ciclos de reloj por segundo) y hay de muchos diferentes valores.

Por otro lado un procesador posee un contador de ciclos es decir un registro el cual informa de cuantos ciclos ha procesado.

Así que tenemos dos fuentes de información que utilizaremos para calcular el tiempo transcurrido ya que si dividimos la cantidad de ciclos que han pasado de un momento a otro entre la frecuencia, obtendremos el tiempo transcurrido con una precisión bastante grande (double).

 

Tenemos:

Frecuencia= ciclos por segundo

Ticks= ciclos procesados de un instante a otro

Tiempo = Ticks / Frecuencia (   ciclos / ciclos por segundo  )

De tal forma que las unidades resultantes serán: segundos.

 

Que!!! segundos? si pero esos segundos están expresados con una gran precisión decimal por lo cual podemos llegar a la precisión de nanosegundos tan solo multiplicando por 1.000‘000.000 (mil millones), y con un tipo de dato double tenemos espacio mas que suficiente para manejar estas cifras.

 

DE DONDE OBTENEMOS LOS DATOS?

Para ello utilizaremos dos funciones de la API de Windows:

  • QueryPerformanceCounter: Retorna el valor almacenado en el registro contador de ciclos del procesador en un momento dado
  • QueryPerformanceFrequency: Retorna la velocidad del procesador

 

Como ven ya esta todo lo necesario, ahora la implementación.

 

IMPLEMENTACIÓN

Lo primero es poder hacer uso de las funciones API  para lo cual nos ayudaremos con DllImport, ya saben la mejor fuente para saber como hacer declaraciones de la API de manera rápida es: http://www.pinvoke.net/. Todo esto lo hare dentro de la clase NanoTemporizador

using System;
using System.Runtime.InteropServices;

public class Temporizador
{
    /// <summary>
    /// Obtiene la frecuencia del procesador
    /// </summary>
    /// <param name="frequency" />variable donde retorna la frecuencia
    /// <returns>True si el procesador tiene contador de frecuencia, false sino</returns>
    [DllImport"kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceFrequency(out long frequency);

    /// <summary>
    /// Obtiene l evalor actual del contador de alto rendimiento del ptrocesador
    /// </summary>
    /// <param name="lpPerformanceCount" />variable donde retorna el valor
    /// <returns>True si todo salio OK, false sino</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
}

Ahora en el constructor de nuestra clase no haremos nada :) . Vale la pena recordar que siempre se deben crear componentes eficientes – según yo :P – por lo que es mejor que tengamos un constructor estático ya que realmente la frecuencia del procesador no cambiara nunca, así que solo es necesario calcularla una sola vez para todas las instancias.

    /// <summary>Almacena la frecuencia del contador de alto rendimiento</summary>
    private static long _frecuencia;

    static NanoTemporizador()
    {
        if (!QueryPerformanceFrequency(out _frecuencia))
            throw new NullReferenceException(
                "Este componente se hizo para utilizar contadores de alto rendimiento. Como no los hay mejor utiliza StopWatch"
             );
    }

Para que funcione realmente como un contador de tiempo necesitamos poder establecer si el contador esta andando o no, para lo cual crearemos una propiedad. Adicionalmente en el método Start del temporizador vams a calcular el valor de contador actual y a cambiar el valor de nuestro indicador a true:

    /// <summary>Almacena el valor de conteo inicial</summary>
    private long _conteoInicial;
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    private bool _isRunning = false;
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    public bool IsRunning { get { return _isRunning; } }

    public void Start()
    {
        if (!_isRunning)
        {
            QueryPerformanceCounter(out _conteoInicial);
            _isRunning = true;
        }
    }

De igual forma se establece el método Stop:

    /// <summary>Almacena el valor de conteo final</summary>
    private long _conteoFinal;

    public void Stop()
    {
        if (_isRunning)
        {
            QueryPerformanceCounter(out _conteoFinal);
            _isRunning = false;
        }
    }

Finalmente se crea una propiedad a travez de la cual podamos hallar el valor en nanosegundos:

    ///<summary>Retorna la cantidad de nanosegundos contados</summary>
    public double ElapsedNanoseconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) * 1000000000L
                   / (double)_frecuencia;
        }
    }

 

Perfecto, eso es todo. Esta es la versión completa:

using System;
using System.Runtime.InteropServices;

public class NanoTemporizador
{
    /// <summary>
    /// Obtiene la frecuencia del procesador
    /// </summary>
    /// <param name="frequency">variable donde retorna la frecuencia</param>
    /// <returns>True si el procesador tiene contador de frecuencia, false sino</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceFrequency(out long frequency);

    /// <summary>
    /// Obtiene l evalor actual del contador de alto rendimiento del ptrocesador
    /// </summary>
    /// <param name="lpPerformanceCount">variable donde retorna el valor</param>
    /// <returns>True si todo salio OK, false sino</returns>
    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

    /// <summary>Almacena la frecuencia del contador de alto rendimiento</summary>
    private static long _frecuencia;

    /// <summary>Almacena el valor de conteo inicial</summary>
    private long _conteoInicial;
    /// <summary>Almacena el valor de conteo final</summary>
    private long _conteoFinal;

    /// <summary>Indica si ya se ha inicializado el timer</summary>
    private bool _isRunning = false;
    /// <summary>Indica si ya se ha inicializado el timer</summary>
    public bool IsRunning { get { return _isRunning; } }

    /// <summary>Valor por el cual se multiplican segundos para pasarlos a nanosegundos</summary>
    private const long NANOSEGUNDOS = 1000000000L;

    /// <summary>Valor por el cual se multiplican segundos para pasarlos a milisegundos</summary>
    private const long MILISEGUNDOS = 1000L;

    static NanoTemporizador()
    {
        if (!QueryPerformanceFrequency(out _frecuencia))
            throw new NullReferenceException(
               "Este componente se hizo para utilizar contadores de alto rendimiento. Como no los hay mejor utiliza StopWatch."
            );
    }

    /// <summary>Inicia el conteo del temporizador</summary>
    public void Start()
    {
        if (!_isRunning)
        {
            QueryPerformanceCounter(out _conteoInicial);
            _isRunning = true;
        }
    }

    /// <summary>Detiene el conteo del temporizador</summary>
    public void Stop()
    {
        if (_isRunning)
        {
            QueryPerformanceCounter(out _conteoFinal);
            _isRunning = false;
        }
    }

    ///<summary>Retorna la cantidad de nanosegundos contados</summary>
    public double ElapsedNanoseconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) * NANOSEGUNDOS
                   / (double)_frecuencia;
        }
    }

    ///<summary>Retorna la cantidad de milisegundos contados</summary>
    public double ElapsedMilliseconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) * MILISEGUNDOS
                   / (double)_frecuencia;
        }
    }

    ///<summary>Retorna la cantidad de segundos contados</summary>
    public double ElapsedSeconds
    {
        get
        {
            return (_conteoFinal - _conteoInicial) / (double)_frecuencia;
        }
    }
}

CÓMO USARLO?

Bien este es un ejemplo tontisimo, pero muy ilustrativo:

using System;

namespace Prueba
{
    class Program
    {
        static void Main(string[] args)
        {
            NanoTemporizador temporizador = new NanoTemporizador();

            Probador(temporizador, 1000);
            Probador(temporizador, 1000);
            Probador(temporizador, 5000);
            Probador(temporizador, 2358);
            Probador(temporizador, 3541);
            Probador(temporizador, 10000);

            Console.ReadLine();
        }

        private static void Probador(NanoTemporizador temporizador, int espera )
        {
            temporizador.Start();
            System.Threading.Thread.Sleep(espera);
            temporizador.Stop();

            Console.WriteLine("Tiempo transcurrido: {0} ns> ", temporizador.ElapsedNanoseconds);
            Console.WriteLine("Tiempo transcurrido: {0} ms> ", temporizador.ElapsedMilliseconds);
            Console.WriteLine("Tiempo transcurrido: {0} sg> ", temporizador.ElapsedSeconds);
            Console.WriteLine("=====================================================");
        }
    }
}

Hasta pronto.

Print Friendly
Share

TweetFollow @JuanKRuiz

  • 4 Comentarios »
  • Publicado en la categoría 'C#'

4 comentarios to “C# – Cuando la precisión que da el StopWatch no es suficiente… ”


  • and Says:
    August 10th, 2009 at 6:01 pm  

    konnichiwa!!

    Es curioso tu aporte, me será útil, gracias por la idea, pero ahora te escribo por una duda muy diferente. Pretendo hacer un programa para la facturación electrónica digital en México, estoy trabajando en c# y tengo días atorado en la generación del sello digital, especialmente en el algoritmo rsa, no encuentro como agregar las llaves (.key) a la encriptación.
    Agradecería mucho tu ayuda o cualquier pista que me sea util, aquí te dejo esta página como referencia:
    http://www.sat.gob.mx/sitio_internet/e_sat/comprobantes_fiscales/15_6544.html

  • C# - Cuando la precisión que da el StopWatch no es suficiente - Ideas de un Conejo Says:
    August 18th, 2009 at 2:58 pm  

    [...] http://juank.black-byte.com/c-medir-nanosegundos/ [...]

  • Jonas Says:
    August 19th, 2009 at 2:21 am  

    mmm… pero el contador mide el tiempo total de la cpu, o el tiempo virtual que se le asigna al proceso? porque evidentemente se pueden producir cambios de contexto entre procesos y si esto no se tiene en cuenta los tiempos obtenidos podrian no ser reales.

    un saludo

  • JuanK Says:
    August 19th, 2009 at 7:53 am  

    Hola Jonas, el contador mide el tiempo total de CPU desde que se inicio el proceso hasta que termino. Por ello ejecutar un proceso dos veces o más no necesariamente te dara los mismos tiempos pues dependemos de los cambios de contexto entre un proceso y otro ( realmente entre todos hilos en ejecución).

Deja un comentario

Redes Sociales

Follow @JuanKRuiz
Answer Questions

Busca en el blog

Artículos Relacionados

  • C# – Recibir notificaciones cuando hayan cambios de sesión parte 1
  • C# – Recibir notificaciones cuando hayan cambios de sesión parte 3 – WPF
  • C# – Recibir notificaciones cuando hayan cambios de sesión parte 2 – Windows Forms
  • Optimización de Código – Utilizando Generics – C#
  • Optimización de Código – Cómo Convertir un Entero en Binario – C#
  • Como cambiar el texto de los botones de un MessageBox – C#
  • C# – El extraño caso de la ventana sin borde que no se deja cambiar de tamaño
  • Artículos Relacionados

  • C# – Recibir notificaciones cuando hayan cambios de sesión parte 1
  • C# – Recibir notificaciones cuando hayan cambios de sesión parte 3 – WPF
  • C# – Recibir notificaciones cuando hayan cambios de sesión parte 2 – Windows Forms
  • Optimización de Código – Utilizando Generics – C#
  • Optimización de Código – Cómo Convertir un Entero en Binario – C#
  • Como cambiar el texto de los botones de un MessageBox – C#
  • C# – El extraño caso de la ventana sin borde que no se deja cambiar de tamaño
  • Nube de Temas

    API - C# - codigo - Fiber - Forms - GeSHi - icon - icono - IE - IE9 - imagenes - IT - operativo - Pinned - PowerShell - Proceso - rendimiento - RSS - sistema - Sistemas Operativos - Site - stack - Thread - velocidad - Visual - WCF - Windows - WndProc - WPF - XNA

    Blogs recomendados

  • VBCodigoPocketPC Espacio para tratar temas de programacion para dispositivos moviles, Pocket PC, Compact Framework, Embbeded Visual Basic, Visual Basic.NET , C# (C Sharp)
  • Róbinson Moscoso Estaré publicando acá cosas sobre tecnologia .NET, situacioines cotidianas de las que voy aprendiendo… sirve como extensión de memoria.
  • .Net C# Blog de Nelsón Venegas
  • Warnov Microsoft Developer Evangelist
  • IT LIfe Blog de mi Hermano que esta en el lado claro: IT
  • Sorey Garcia Una chica del común con la firme intención de no serlo
  • Black Byte videojuegos, modelado y animación 3d
  • Road to IT World Cosas interesantes de IT
  • Marcela Chitiva Un poco de esto… un poco de aquello
  • Surviving the Nigth El mejor blog para aquellos que nos gustan los “internals”
  • Meta

    1. Log in
    2. WordPress

    Ideas de un Conejo is powered by Wordpress. Theme designed by Juan Carlos Ruiz.