Go to content Go to navigation Go to search

Optimización de Código – Utilizando Generics – C#

April 19th, 2010 by JuanK

Habitualmente en el proceso de desarrollo de nuestros procesos de desarrollo de software hacemos uso de lo comúnmente llamado Colecciones, es decir arreglos de diferente para solucionar determinados problemas.

Normalmente esto no representa mayor problema hasta que nos encontramos con entornos reales de trabajo, aplicaciones cliente servidor o paginas web accesadas de manera simultanea por decenas o miles de usuarios, o como caso mas crítico un videojuego donde uno o dos usuarios (o a veces decenas o miles de ellos…)realizan millones de operaciones por segundo en nuestros procesadores gráficos y CPU’s.

Las colecciones en estos casos se empiezan a volver un problema, porque?. Bien habitualmente muchos de los elementos que utilizamos en el desarrollo son tipos valor como por ejemplo long (Int64), int (Int32), float(Single), double (Double), estructuras (struct) o enumeraciones (enum) los cuales utilizamos ampliamente con el fin de agilizar el rendimiento de nuestra aplicación, pero lastimosamente en el proceso de incluir estos tipos de dato en una colección estos son encajonados (en adelante se hablará boxing) en un tipo object; Esto se debe a que las colecciones para poder almacenar cualquier valor dentro de si almacenan tipos object.

Gracias a Dios o en lo que sea que crean uds.) el CLR 2.0(conocido por la mayoría como .net Framework 2.0) soporta el uso de Generics los cuales no son mas que una cualidad que permite la implementación de código sin tener totalmente definido el/los tipo(s) de dato que esos metodos usan… pero bueno la idea de este hilo no es hacer un curso de generics, así que en resumen gracias a los generics existen un conjunto de colecciones genericas que se traducen en colecciones a las cuales se les puede indicar que tipo de dato almacenaran (entre otras cosas) lo cual se convierte en la eliminación del proceso de boxing y unboxing para poder manipularlas.

Como acotación adicional los desarrolladores de C++ conocen los generics de la STL (Standard Template Library) por lo cual no suelen llamarlos generics sino templates.

Utilizar generics SI es mucho más rápido

Bien, se preguntaran el porque del titulo… así que antes de entrar en materia les contare…

En varios textos de programación en C# y java los autores suelen utilizar unos recuadros con casos reales o notas al margen, y aunque no se el numero exacto si se que ya son varias veces que he visto mencionar que en la practica las colecciones que usan generics no se muestran mucho mas rápidos que las que utilizan el un/boxing tradicional… lo cual es mentira y me pregunto… en que proyectos han trabajado ellos? tal ves en proyecos web y seguramente no en los grandes proyectos web… lo que sin lugar a dudas es cierto es que un desarrollador que tenga experiencia en el montaje de grandes portales, de aplicaciones cliente servidor con gran numero de usuarios o programadores de videojuego o de aplicaciones científicas los desmentirían por completo. Y este post es para eso para desmentir esa falsa creencia y mostrar la realidad: Las colecciones genericas son mucho más eficientes que las no genéricas.

Demostración

Lo primero que haremos sera crear una pequeña aplicación por consola en C# en la cual declararemos esta constante y la siguiente función:

private static long Boxing_Unboxing()
{
    long suma = 0;
    ArrayList arrayEnteros = new ArrayList();
 
    for (int i = 0; i < tamano; i++)
        arrayEnteros.Add(i);
 
    for (int i = 0; i < tamano; i++)
        suma = suma + (int)arrayEnteros[i];
    
    return suma;
}

No requiere mucha explicación, esta función efectúa el llenado (boxing) de una colección con un numero determinado de elementos y seguidamente suma todos estos elementos en un acumulado (ahí hace unboxing) el cual retorna.

Ahora adicionaremos esta función que hace exactamente lo mismo pero utilizando colecciones genericas:

private static long Generics()
{
    long suma = 0;
    List<int> arrayEnteros = new List</int><int>();
 
    for (int i = 0; i &lt; tamano; i++)
        arrayEnteros.Add(i);
 
    for (int i = 0; i &lt; tamano; i++)
        suma = suma + arrayEnteros[i];
    
    return suma;
}
</int>

Ahora en el método main crearemos el código necesario para invocar las dos funciones anteriores, adicionalmente mediremos el tiempo de ejecución de las mismas para lo cual utilizaremos System.Environment.TickCount y finalmente incluiremos código para medir el consumo de memoria de la aplicación para lo cual haremos uso de dos metodos de la clase estática GC, GC.GetTotalMemory para saber el consumo total de memoria en un momento dado y GC.Collect para liberar los recursos que ya no estan en uso. El código queda así:

const int  tamano = 30000000;
static void Main(string[] args)
{
    int tiempo =0;
 
    tiempo = System.Environment.TickCount;
    long suma = Boxing_Unboxing();
    tiempo = System.Environment.TickCount - tiempo;
    Console.WriteLine(&quot;Resultado: &quot;+suma.ToString()+&quot; Tiempo Un/Boxingt:&quot;+tiempo.ToString().PadLeft(10,' ')
                    + &quot; Memoria: &quot;+GC.GetTotalMemory(false).ToString().PadLeft(7,' '));
 
    GC.Collect();
    
    tiempo = System.Environment.TickCount;
    suma = Generics();
    tiempo = System.Environment.TickCount - tiempo;
    Console.WriteLine(&quot;Resultado: &quot; + suma.ToString() + &quot; Tiempo Genericst:&quot; +tiempo.ToString().PadLeft(10, ' ')
                     +&quot; Memoria: &quot; + GC.GetTotalMemory(false).ToString().PadLeft(7, ' '));
    GC.Collect();
    Console.ReadLine();
}

Una vez ejecutado obtendremos los siguientes valores (o muy parecidos dependiendo del computador de cada cual):

Resultado: 199999990000000 Tiempo Un/Boxing : 4695 Memoria: 883595136
Resultado: 199999990000000 Tiempo Generics  :  764 Memoria: 202279736

Como ven en los resultados, generics se muestran aproximadamente 6 veces más rápido que la técnica tradicional y fue casi 5 veces mas efectivo en el uso de la memoria.

Como desconozco que hardware tienen ustedes, pueden hacer pruebas modificando el valor de la constante tamano de acuerdo alas características de su procesador y memoria, esto porque si exceden el limite de almacenamiento en memoria de sus máquinas el sistema operativo iniciara el proceso de swap a disco poniendose la cosa muuuy lenta… ahí obtendrán diferencias de tiempo mucho mayores incluso proporcionalmente.

Hasta la próxima.

Bookmark and Share

Optimización de Código – Cómo Convertir un Entero en Binario – C#

April 18th, 2010 by JuanK

El título de este post debería ser realmente: "Cómo formatear una cadena para mostrar un entero en formato binario"… pero créanme que nadie lo buscaría así en un buscador :P .

Bien esto parece ser un problema recurrente alrededor de los foros en internet, existen muchas soluciones diferentes a este problema, incluso hay algunos lenguajes que ya incluyen soporte para hacer esto fácilmente,  el CLR no se caracteriza por tener una funcionalidad fácil de utilizar sin embargo en este artículo exploraremos varias soluciones (desde luego no todas )posibles hasta llegar a la solución ideal que espero les sea de provecho a todos.

Estas son las opciones que exploraremos, son básicamente 2.

La primera opción no requiere de mucho análisis pero tiene sus inconvenientes digamos menores. La segunda opción es la que más nos importa, aprenderemos a deducir el algoritmo a implementarlo y subsecuentemente lo iremos optimizando hasta lograr una versión digna de un excelente programador:

  1. Convertir con BitVector32 (mala solución pero rápida de implementar)
  2. Convertir creando un algoritmo eficiente
    • Utilizando string
    • Utilizando StringBuilder
    • Utilizando punteros con codigo inseguro

 

1.  CONVERTIR CON BITVECTOR32

Esta es la solución más rápida al problema, pero en mi opinión la menos profesional….

BitVector32 es una colección especializada que se encuentra en:

System.Collections.Specialized

Esta clase provee una estructura simple que almacena valores boléanos que representan un entero de 32 bit.  Que que?

Ok esta explicación no necesariamente es clara para todos, hagámoslo muy simple veamos un entero de 32 bits, imaginemos que en memoria se ve así:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

Es decir cada casilla representa un bit, para completar así 32 bits.

Bien entonces esto a final de cuentas es una colección bits, es decir en cada ‘celda’ podemos tener dos valores posibles 1 o 0… true o false.

Bien eso es lo hace BitVector toma un entero y lo permite manejar como un array de booleanos.

El método ToString() permite convertir todo esto a 1s y 0s para poder ver los valores de cada ‘celda’ en su representación numérica, sin embargo esto tiene un inconveniente pues si utilizamos elObjetoBitVector.ToString() recibiremos algo así como esto:

BitVector32{00000000000000000000000001101111}

Nos sobra algo al principio y al final… a nosotros solo nos interesan los datos binarios así que la solución es hacer un substring de la parte de la cadena que nos interesa, finalmente el código quedaría así:

public static string EB_BitVector32(Int32 entero)
{
    BitVector32 bv = new BitVector32(entero);
    return bv.ToString().Substring(12, 32);
}

Esto nos produciría este resultado similar a:

00000000000000000000000001101111

 

2.  CONVERTIR CREANDO UN ALGORITMO EFICIENTE

Ahora si entremos en materia hagamos el algoritmo.

Una de las primeras ideas que a uno se le viene a la mente es utilizar el conocido algoritmo que nos permite convertir un numero decimal a base 2, para el que no lo recuerde o no lo conozca: es un algoritmo que toma como base divisiones anidadas del valor entre 2 y recupera los residuos. En este link tienen una explicación detallada al respecto: Decimal a Binario

Sin embargo dicho algoritmo puede ser ineficiente para nuestro gusto.

Retomemos conceptos como puntos clave que nos permitan llegar a un algoritmo óptimo:

  1. Básicamente un entero es un arreglo de 4 bytes (32 bits)
  2. Por cada bit se debe producir su equivalente como cadena ("0" , "1") según si el bit esta prendido(1) o apagado(0)
  3. Podemos acceder a todos los bit como un arreglo de boolean a través de BitVector32, pero en ese caso la solución sería aún más lenta que utilizando la propuesta inicial que usa BitVector32
  4. Hay que encontrar el mecanismo adecuado para recorrer los bit

De los conceptos clave tenemos que los dos primeros nos son útiles, el tercero no nos aporta nada nuevo y el cuarto es nuestra necesidad más inmediata: Averiguar como recorrer los bit sin necesidad de usar BitVector32.

Lo primero que me salta a la mente es usar operadores binarios especialmente corrimientos de bit (<<) y and (&) para las máscaras.

 

Veamos un poco de teoría acerca de lo que haremos.

  1. Crear un bucle e 32 ciclos
  2. Leemos siempre el valor del primer bit del entero de 32 bit, esto lo podemos hacer efectuando un and binario (&) del entero con un numero que tenga el primer bit prendido únicamente, es decir con : 0×80000000 (es decir 2147483648 )… porqué este número?  bueno coloquemos este 0×80000000 en nuestro arreglo de 32 bits:
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    8       0       0       0       0       0       0       0      

    Cada 4 bits representan uno de los dígitos hexa, de esta forma el 0×8 esta representado por los primeros 4 bits, en efecto 1000 = 0×8, así que si se fijan en nuestro arreglo de bits solo tenemos un bit prendido, de tal forma que cualquier cosa que haga & con 0×80000000 solo podría llegar a tener prendido el bit 1, así podemos hallar que valor tiene el bit 1 de un número dado ya que si ese bit esta en 0 al hacer and quedaría en 0, mientras que si esta en 1 al hacer and quedaría en uno nuevamente. Concretando, cuando hacemos un and entre dos enteros y uno de los enteros es 0×80000000 el resultado siempre será el valor del primer bit del otro entero. ( espero haber sido claro).

  3. En cada iteración corremos 1 bit posición a la izquierda (<< 1), de tal forma que el primer bit es reemplazado por el bit siguiente
  4. Se regresa al paso 2

IMPLEMENTACION CRUDA Utilizando String

Este es el código resultante de la implementación más simpe del algoritmo:

public static string EB_String(int entero)
{
    //La máscara y el # de iteraciones
    const uint mascara = 0x80000000;
    const int iteraciones = 32;
    //el contador y el resultado
    int contador = 0;
    string resultado = &quot;&quot;;
 
    //Se recorren los 32 bit
    while (contador++ &lt; iteraciones)
    {
        /*Si el entero and la mascara = 0 quiere decir
         *que el bit 1 esta apagado*/
        if ((entero &amp; mascara) == 0)
            resultado += &quot;0&quot;;
        else
            resultado += &quot;1&quot;;
 
        /*correr un bit a la izquierda para poner
         *el siguiente bit en la posicion del primero*/
        entero = entero &lt;&lt; 1;
    }
    return resultado;
}
 

OPTIMIZACIÓN DE CÓDIGO Utilizando StringBuilder

La implementación anterior es susceptible de una sencilla mejora utilizando StringBuilder ya que al ser string un tipo inmutable , las recurrentes concatenaciones generadas al utilizar masivamente el método sobrecargarían el GC, sin contar el overhead producido por las frecuentes reservas de memoria (1 adicional por cada cambio al string). StringBuilder se encuentra en el namespace System.Text.

Código optimizado utilizando StringBuilder:

public static string EB_StringBuilder(int entero)
{
    //La máscara y el # de iteraciones
    const uint mascara = 0x80000000;
    const int iteraciones = 32;
    //el contador y el resultado
    int contador = 0;
    StringBuilder resultado = new StringBuilder(iteraciones);
 
    //Se recorren los 32 bit
    while (contador++ &lt; iteraciones)
    {
        /*Si el entero and la mascara = 0 quiere decir
         *que el bit 1 esta apagado*/
        if ((entero &amp; mascara) == 0)
            resultado.Append('0');
        else
            resultado.Append('1');
 
        /*correr un bit a la izquierda para poner
         *el siguiente bit en la posicion del primero*/
        entero = entero &lt;&lt; 1;
    }
    return resultado.ToString();
}

OPTIMIZACIÓN DE CÓDIGO Utilizando punteros – código unsafe

Aunque StringBuilder ya proporciona una mejora muy importante, dadas las características de nuestro problema(concatenación uno a uno) resulta mucho más idóneo generar código que utilice punteros. Es importante tener en cuenta que para que este código funcione se requiere modificar las propiedades del proyecto para que admita código unsafe.

Esta es la versión básica con manejo de apuntadores:

public static unsafe string EB_Unsafe(int entero)
{
    const uint mascara = 0x80000000;
    const int iteraciones = 32;
 
    int contador = 0;
 
    //Se reservan 32 posiciones y uno adicional para 
    //terminacion en null
    char* resultado = stackalloc char[iteraciones + 1];
    //puntero de trabajo
    char* aux = resultado;
 
    while (contador++ &lt; iteraciones)
    {
        if ((entero &amp; mascara) == 0)
            *aux = '0';
        else
            *aux = '1';
 
        //Mover el puntero una posicion dentro de la cadena
        aux++;
 
        entero = entero &lt;&lt; 1;
    }
    return new string(resultado);
}

Esta es la versión final de "bonus" totalmente optimizada para velocidad de procesamiento, es levemente diferente de la anterior para realizar la menor cantidad de validaciones posibles y para que ignore los 0s a la izquierda, lo cual la hace excepcionalmente rápida en la mayoría de los casos. Revísenla…:

public static unsafe string EB_Unsafe_Opt(int entero)
{
    const uint mascara = 0x1;
    const int iteraciones = 32;
 
    char* resultado = stackalloc char[iteraciones + 1];
    char* aux = resultado + iteraciones - 1;
 
    do
    {
        if ((entero &amp; mascara) == 0)
            *(aux--) = '0';
        else
            *(aux--) = '1';
 
        entero = entero &gt;&gt; 1;
    } while (entero != 0);
 
    return new string(++aux);
}

Si alguien tiene una solución mejor por favor no dude en compartirla!!

En unos días publicare un programa especialmente diseñado para medir la velocidad de cada una de estas alternativas… les adelanto que las diferencias de tiempo van casi hasta de 20 o 30 veces más velocidad.

 

Happy Learning!!!

OOOHH OLvidaba decirles Si quieren pueden usar:

System.Convert.ToString(elValor, 2);

jajajajaja, bueno el ejercicio que hicimos produce un resultado 30% más rápido. :P

Bookmark and Share

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

February 2nd, 2010 by JuanK

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.

Bookmark and Share