Cómo no escribir código concurrente en .NET

En muchas ocasiones el código que se escribe en .NET se tiene que tener en cuenta que puede ser llamado de manera concurrente, es decir, desde varios hilos del sistema operativo. En ese caso hay que hace que el código sea lo más óptimo posible para no generar muchas esperas y bloqueos innecesarios del código.

Lo más sencillo

La forma de empezar a bloquear código para asegurarse de que el código sólo es ejecutado por un único Thread es envolver ese código con la palabra reservada lock (en C#), un ejemplo de ese código se puede encontrar a continuación.

public class LockExample
{
    private object syncLock = new object();

public void MethodWithLock()
{
lock(syncLock)
{
// código
}
}
}

 

De este tipo de bloqueo se pueden encontrar variantes, pero que en esencia son lo mismo. Utilizando el atributo MethodImpl con el valor de MehtodImplOptions.Synchronized se consigue el mismo resultado que es bloquear todo el cuerpo de la función utilizando la palabra reservada lock. La diferencia es que cuando el método es de instancia se utiliza el objeto this para señalar el bloqueo, mientras que cuando el método es estático se utiliza el typeof de la clase para hacer el bloqueo.

MethodImpl para métodos de instancia

Este código que utiliza MethodImpl,

[MethodImpl(MethodImplOptions.Synchronized)]
public void MethodWithLock()
{
}

 

Es exactamente igual a:

public void MethodWithLock()
{
    lock(this)
    {
    }
}

 

MethodImpl para método estáticos

Para los método estáticos utilizar MethodImpl

public class LockExample
{
    [MethodImpl(MethodImplOptions.Synchronized)]
    public static void MethodWithLock()
    {
    }
}

 

Es igual a escribir el siguiente código:

public class LockExample
{
    public static void MethodWithLock()
    {
        lock (typeof(LockExample))
        {
        }
    }
}

 

Como se ha comentado anteriormente este tipo de bloqueos no son recomendables en ningún caso, porque aumenta la granularidad del bloqueo y no se tiene un control sobre las operaciones de lectura o escritura de las variables que se desea acceder. Además, en caso de excepción no está claro si el bloqueo se libera o se queda para siempre.

Queda también comentar que en caso concreto del bloqueo para los métodos estáticos además se puede incurrir en un comportamiento muy peculiar del CLR que se llama Marshal-by-bleed.

Marshal-by-bleed

.NET Framwork soporta marshalling de objetos entre dominios de aplicación llamado marshal-by-bleed. Esto significa que cuando se tienen varios dominios de aplicación dentro del mismo proceso de .NET entre estos dominios de aplicación, si no se ha especificado de LoaderOptimization, los ensamblados firmados con un nombre fuerte (strong name) serán compartidos entre los dominios de aplicación. Pues bien, eso puede llevar a que la referencia en memoria de objetos estáticos referenciados desde un GCRoot pueda ser el mismo entre varios dominios de aplicación. En efecto prácticos, la llamada a typeof(String) puede ser la misma referencia entre dominios de aplicación. Sabiendo esto si el código de más arriba la clase que se utiliza como bloqueo en el atributo MethodImpl es una clase que forma parte de un ensamblado firmado con un nombre fuerte, el hecho de utilizar ese objeto puede hacer que el mismo código ejecutado en otro dominio de aplicación diferente (dentro del mismo proceso) bloquee el otro dominio de aplicación. Así de esta manera se están produciendo bloqueos a través de dominios de aplicación, una situación que es bastante complicada de detectar en aplicaciones en producción.

Para más información Unai Zorilla escribió un artículo en 2009 con un ejemplo sobre esto (http://geeks.ms/blogs/unai/archive/2009/02/08/marshall-by-bleed-explained.aspx) también Joe Duffy escribió sobre esto en 2006 (http://joeduffyblog.com/2006/08/21/dont-lock-on-marshalbybleed-objects/)

Mejorando el código concurrente

Como se ha visto en los anteriores ejemplos, este tipo de código no es la mejor solución para controlar el acceso a métodos o variables. Otra opción es utiliza la clase Monitor, que permite entre otras cosas poner un timeout para que en caso de que el bloqueo dure demasiado tiempo, tener un mecanismo para poder abortarlo.

Otra opción para aumentar la granularidad de los bloqueos es utilizar una clase que permita tener diferentes patrones de acceso a recursos compartidos. Uno de los más utilizados es un escritor varios lectores, que se basa en la idea de tener una único thread escribiendo un varios leyendo a la vez. Para hacer eso dentro de .NET Framework hay una clase llamada ReaderWriterLockSlim que permite justamente crear este patrón.

En el siguiente código se protege el acceso a una variable entera para que se pueda leer desde muchos threads pero sólo se pueda escribir desde uno.

public class MultipleReadsOneWriter
{
    private volatile int value;
    private ReaderWriterLockSlim rwls;

public MultipleReadsOneWriter()
{
rwls = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
}

public int ReadValue()
{
int result = default(int);
rwls.EnterReadLock();
try
{
result = value;
}
finally
{
rwls.ExitReadLock();
}
return result;
}

public void WriteValue(int number)
{
rwls.EnterWriteLock();
try
{
value = number;
}
finally
{
rwls.ExitWriteLock();
}
}

public void WriteValueIfEqual(int compare, int number)
{
rwls.EnterUpgradeableReadLock();
try
{
int current = value;
if (current == compare)
{
rwls.EnterWriteLock();
try
{
value = number;
}
finally
{
rwls.ExitWriteLock();
}
}
}
finally
{
rwls.ExitUpgradeableReadLock();
}
}
}

 

Algunos detalles interesantes sobres este código. La clase ReaderWriterLockSlim tiene métodos para poder bloquear para sólo lectura, sólo escritura y también para una lectura que tiene posibilidad de actualizarse a una escritura. De esta manera se controla mucho mejor las lecturas y escritura de una variable.

Otro detalle interesante de código es que cualquier interacción sobre las llamadas de la clase ReaderWriterLockSlim está envuelta entre un try/finally que permite asegurarse que siempre se vaya a llamar a la funciona de salida de la operación actual. Esto es muy importante porque evita que se tengan bloqueos huérfanos que nunca se liberen.

Luis Guerrero.

Technical Evangelist Microsoft Azure.

@guerrerotook

Pruebas de rendimiento Web para Windows Azure

Una de las preguntas más recurrentes que suele tener los clientes a la hora de trabajar en Windows Azure suele ser, ¿cuál será el rendimiento de mi sitio web en Azure?

La respuesta a esta pregunta no es sencilla o universal. Depende de muchos factores, del tamaño de la máquina utilizado en el servicio en la nube, de la tecnología, cuantas consultas se hagan a la base de datos, ect. El número de factores que pueden determinar ese número final es amplísimo, así que no se puede dar una respuesta para todos los casos.

Entonces ¿cuál es la mejor aproximación para dar respuesta a esa pregunta?

Medir. Medir. Medir.

Pero antes de medir el tiempo que tarda el sitio web en responder, cabe mencionar algunos artículos creados por otros desarrolladores sobre cómo aumentar el rendimiento de las aplicaciones web.

Coding Horror – Performance is a Feature

Scott Hanselman – Penny Pinching in the Cloud: Enabling New Relic Performance Monitoring on Windows Azure Websites

Medir

La mejor manera para saber si una aplicación web es lenta o rápida es medir el tiempo que tarda una respuesta. Esto permite tener una media real de como el sitio web funciona, pero eso no es suficiente. Se necesita además, simular cierta carga, varios usuarios, con varios patrones de uso de la web. Y no solo hacer una sola petición, sino una navegación completa del usuario comprobando cookies, parámetros de la url, inicios de sesión, ect.

Una vez que se ha establecido un escenario base, uno puede empezar a realizar optimizaciones en su código para ver cómo se mejora ese tiempo. Es importante realizar estas mediciones en el rendimiento una vez que se ha realizado el sitio web, o por lo menos cuando está a punto de cerrarse el proyecto. No tiene ningún sentido empezar a hacer optimizaciones tempranas, ya que son un anti patrón muy claro.

Premature-Optimization and Performance Anxiety

Performance anti patterns

Visto todo lo anterior, lo ideal sería disponer de algún software que nos permite grabar la navegación de un usuario y que después sea capaz de volver a simular todos los pasos que ese usuario ha hecho. Justamente estamos hablando de los Web Performance Load Test de Visual Studio Ultimate.

Web Performance and Load Test

Visual Studio en su versión Ultimate, incluye un tipo de proyecto dentro de la categoría de Test llamado Web Performance and Load Test, que permite grabar sesiones web.

clip_image002

Cuando se ha creado el proyecto, hay un fichero con extensión de tipo .webtest donde aparecerán todas las peticiones HTTP que se han grabado para esa sesión. Cada fichero representa una colección de peticiones HTTP que pueden ser una historia de usuario, una navegación sobre una característica a medir, o simplemente una invocación a una API Rest.

clip_image004

Se puede hacer una grabación con Internet Explorer donde se registran todos los pasos que el usuario ha hecho en el sitio web.

clip_image005

clip_image006

Una vez que ha terminado de hacer la grabación aparecerán en Visual Studio la lista de Urls a testear.

También se puede utilizar un origen de datos para obtener las urls y así hacerlos parte un test de integración.

Por supuesto se pueden agregar a mano las peticiones, las condiciones para dar como válida una petición y todo lo relacionado con el ciclo de vida de una petición.

clip_image008

Una vez que se ha confeccionado la lista de las Urls que forman parte del test, se puede pasar a hacer la prueba de rendimiento o se puede personalizar las peticiones. Hay un botón en la barra del test que permite generar el código en C# asociado de las peticiones para poder modificarlo a petición del usuario, de esta manera se puede automatizar mucho más el proceso.

clip_image010

Prueba de carga

El siguiente paso es generar y configurar una prueba de carga para la prueba web recién creada. Dentro del proyecto de Test hay que pulsar en añadir nuevo elemento y seleccionar prueba de carga (Load Test).

clip_image012

En el cual inmediatamente creará un asistente en el que se pueden configurar las diferentes opciones para la prueba de carga.

clip_image014

1. Darle un nombre al escenario

clip_image016

2. Ahora se seleccionan el patrón de carga, como se desean simular los usuarios de la prueba. Se puede elegir un valor constante de usuarios y otro incremental en el que se pueden configurar todas las opciones de incremento.

clip_image018

3. En la siguiente pantalla se puede elegir la manera en la que se mezclan los diferentes escenarios de prueba (que son las pruebas web que se han definido antes).

clip_image020

4. En esta pantalla es donde se agregan los diferentes test que se van a formar parte de las pruebas y que tanto porciento representan.

clip_image022

5. Ahora se pueden seleccionar las velocidades de red para calcular el tiempo de descarga.

clip_image024

6. Mezcla de navegadores

clip_image026

7. Ahora se pueden seleccionar las máquinas de las que se quieren obtener los contadores de rendimiento, en el caso que se está tratando ahora (Windows Azure) tendríamos que abrir los puertos de WMI para poder acceder a esa información, pero si se está haciendo una prueba en local con otra máquina se puede poner el nombre de la máquina y qué contadores de rendimiento se quieren monitorizar.

clip_image028

8. La última opción define cuánto tiempo durará la prueba.

clip_image030

Por supuesto todas estas opciones pueden ser modificadas posteriormente en cualquier momento. Una vez finalizado el asistente, en Visual Studio aparece esta pestaña que contiene las propiedades del proyecto de carga

clip_image032

Recopilando datos

La prueba de carga consiste en recopilar datos de muchos contadores, no solo de las máquinas en las que se ejecuta la web, sino de los agentes que ejecutan las pruebas de cargas. Eso hace que una de las opciones a la hora de guardar las muestras de la prueba sea una base de datos SQL Server. Desde la opción de administrar el controlador de la prueba, se puede acceder al almacén donde se guardarán los datos.

clip_image033

Desde aquí se puede configurar en qué instancia de SQL Server se guardaran los datos de muestra. La base de datos tiene que tener un esquema predeterminado para que funcione correctamente. En el directorio de instalación de Visual Studio hay un fichero llamado (C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE) loadtestresultsrepository.sql que contiene el script necesario para generar la base de datos desde cero.

Ejecutando la prueba

Una vez que se ha configurado todas las opciones de la prueba de carga, se puede proceder a ejecutar la prueba. Visual Studio ofrece un panel de control para ver toda la información sobre la prueba, los contadores y los resultados de manera provisional. Toda la información que se recopila se guarda en la base de datos antes configurada y se puede volver a abrir para consultar los datos.

Panel de información

Cuando se ha acabado la prueba y se vuelve a abrir el resultado, primero aparece un resumen de todo lo ejecutado.

image

Donde se puede visualizar información relativa a cuando se ha ejecutado la prueba, los principales valores del resultado; carga de usuarios, media de tiempo de respuesta de la web, número de peticiones por segundo, ect.

Además de esta información, se puede visualizar individualmente por cada una de las web que forman parte de la prueba, la información del número de peticiones y de tiempo medio de respuesta.

Al final de todo aparece el detalle del controlador y de los agentes que han realizado la prueba.

Graficas

Otra pestaña muy importante para entender los resultados de la prueba de carga es la de gráficas. En esta pestaña se puede visualizar, a lo largo del tiempo, los valores que se deseen. De manera predeterminada Visual Studio ofrece un cuadrante de cuatro gráficas, donde se pueden añadir métricas sobre las diferentes opciones de la prueba de carga.

image

Conclusión

Con las pruebas de carga Web de Visual Studio se pueden hacer mediciones del tiempo medio de respuesta de los sitios web, numero de excepciones, número de peticiones por segundo antes de dar un error de ocupado, ect.

De esta manera se puede hacer una prueba de carga directamente apuntando a Windows Azure, para ver cuantas peticiones son capaces de responder los servidores antes de dar errores 500. Es importante hacer estas pruebas con el número de servidores que se utilizará en producción para intentar que el entorno de pruebas de carga sea lo más similar al entorno final.

Luis Guerrero.

@guerrerotook

El recolector de basura

Todo desarrollador que haya trabajado con .NET, alguna vez ha escuchado hablar del recolector de basura. En este artículo vamos a intentar poner un poco de luz sobre ese concepto, muchas veces misterioso para los programadores.

¿Por qué existe o necesitamos un recolector de basura?

El CLR es un maquina virtual en el que se ejecutan nuestras aplicaciones y .NET es un framework. Microsoft hizo este framework para tener una capa de abastración entre el sistema operativo y las aplicaciones. Una de las cosas más problemáticas en cuanto al desarrollo de aplicaciones es la gestión de memoria. La memoria no es finita y necesita de una gestión, reservar, liberar, compactar, reorganizar. Es por esto que .NET tiene el recolector de basura, para ayudarnos a recolectar los elementos no utilizados y reorganizar la memoria.

Esta caracteristica permite que podamos usar los objetos detro de nuestros lenguajes de programación sin tener en cuenta como se reclican, nostros hacemos el new y el recolector de basura, cuando el objeto ya no sea usado, lo recolectará.

¿Como se produce esta recolección de basura? y ¿como el GC sabe que objetos no se están usando?.

Como todos sabreis todos los objetos del framework son referencias a objetos, y cuando nosotros igualamos (a excepción, claro, de que esté sobrecargado el operador ==) lo que estamos haciendo es copiar la referecia en memoria donde está el objeto, es decir copiarmos su dirección y no su contenido. Esto tambien se aplica a los ValueType (structs) solo que el framework trabaja de otra manera, pero eso está fuera de este articulo.

El hecho de que todo sean referencias, hace que cuando nostros tenemos una clase y dentro de esa clase tenemos un campo de un tipo, lo que realmente tenemos es la dirección de memoria donde vive el objeto que referenciamos, lo podemos simplificar en que tenemos un grafo, una serie de elementos representados por vertices y por las aristas que son las referencias entre objetos.

Pues bien cuando nosotros creamos un objeto y lo asignamos lo que estamos haciendo es añadiendo un vertice a nuestro grafo de referencias. Así que una vez que establecemos un objeto a null a menos que tengamos otra referencia (arista) a nuestro elemento ese objeto está completamente aislado del grafo y nadie puede acceder a el.

Pues sabiendo esto, ¿como el GC es capar de saber que un objeto ya no es necesario y puede ser recolectado?, teniendo en cuenta que nadie lo referencia. El secreto está en el heap. El heap mantiene una colección de todos los elementos que el runtime ha creado, es decir, de todos los objetos que tenemos en nuestro grafo pero en formato de lista. Pues bien lo que el GC hace es coger un objecto y saber si hay alguna referncia a ese objeto (una arista) si no es capaz de encontrar una arista hasta ese objeto es porque el objeto nadie lo está referenciado y por eso puede ser recolectado con seguridad. Así se simple.

Ahora bien, el recolector de basura no enumera todos los objetos que estan en el heap y por cada uno de ellos recorre todo el grafo para encontrar una referencia a ese objeto, en vez de eso lo que utiliza son unos objetos raiz, llamados GCRoots, por los cuales empieza el grafo de nuestra aplicación.

Todo esto que he contado como podemos tener evidencias de que es cierto, teniendo un entorno de depuración montado con simbolos y WinDBG + SOS. (Podeis encontrar información de cómo configurar el entorno de depuración en mi blog)

· !dumpheap para mostrar todos los elementos del heap

· !gcroot 0123292 para mostrar cuales son las referencias a un objeto que puede ser:

o En la pila

o En un GCHandle

o En un objeto listo para la finalización

o O en un objeto encontrado el los lugares anteriores

Además de todo eso si sois aventureros y os gusta las experiencias fuertes, podemos visualizar gráficamente el grafo de refencias con sus GCRoots a través una herramienta de profilling que tiene Microsoft, CLRProfiler disponible en www.microsoft.com/downloads

clip_image002

Aquí tenemos una captura del grafo de nuestra aplicación de ejemplo, en la que podemos ver el <root> del que os hablaba.

¿Qué son las generaciones?

Las generaciones son agrupaciones de las edades de los objetos en memoria. Cuando un objeto se crea está en la generación 0, si se produce una recoleccion de basura lo supervivientes de la generación 0, se les promueve a la generacion 1, y la generación 0 se queda libre y compactada. Se empiezan a crear objetos de nuevo, se lanza otra recoleccion de basura de 0 a 1, los objetos que sobreviven de la generacion 1 pasan a la 2 y los que sobreviven de las generacion 0 pasan a la 1, así sucesivamente.

Solamente existen 3 generaciones, la 0, 1 y 2. Normalmente donde más objeto se generan y se destruyen es en la generación 0 porque es la más usada.

Tamaños:

· Generacion 0: 256 kb (cache segundo nivel del procesador)

· Generacion 1: 2Mb

· Generacion 2: 10Mb

Además de todo eso hay una generación especial para los objetos muy grandes en el framework, LOH (Large object heap), que son los objetos con más de 64kb

¿Cuándo se lanza una recoleccion de basura?

Esta es un pregunta complicada porque no tiene una respuesta directa, hay maneras por el cual se puede generar una recolección de basura o por cuales se puede retrasar. Lo más importante a saber es que las recolecciones de basura se hacen cuando la generacion 0 del GC está llena y se va a crear un nuevo objeto, es decir la recoleccion de basura no se produce cuando se llena la memoria, sino cuando se llena la memoria y se intenta crear un objeto. Además de eso hay que tener en cuenta varios factores. El recolector de basura tiene una herustica que le permite tunearse para ofrecer el máximo rendimiento en cada tipo de aplicación, además de que tiene un historial de las acciones que realiza. Por ejemplo, una de las cosas más importantes para el GC es la cantidad de memoria que libera, no la cantidad de objetos que recolecta, si por ejemplo se lanza una recoleccion de memoria y se liberan 150000 objetos que representa 23kb, seguramente el recolector de basura hará que crezca más la memoria de la generacion 0 antes de hacer otra recoleccion, porque lo que le interesa es recolectar mucha memoria no muchas referecias. En ese sentido si el recolector de basura se encuentra con que ha recolectado 150 objetos que ha sido un total de 400kb seguramente la siguiente vez que llene la generación 0 automaticamente generará una nueva recoleccion de basura.

Porque nunca deberia de llamar a GC.Collect

Como he explicado antes el recolector de basura tiene su propia heurística que le permite tunearse de manera automática y sin intervención del programador. En las primeras versiones de .NET se podía llamar a la función GC.Collect (que teóricamente fuerza una recolección de basura), pero que en realidad lo que hacía era sugerir una recolección de basura. En ese sentido muchos desarrolladores se quejaron porque en sus aplicaciones llamaban incesantemente al recolector de basura pero no se lanzaba ninguna recolección (lo sabían porque miraban los contadores de rendimiento de .NET), así que Microsoft tuvo que dar marcha atrás e incluir una sobrecarga en la llamada de GC.Collect que aceptaba por parámetro un entero con la generación máxima en la cual se iba a producir la recolección, y los más importante de todo, un enumerado de tipo GCCollectionMode que permitia decir si la recolección era forzada u optimizada.

Como podeis imaginar el modo optimizado es el predeterminado, es decir, el modo en el que le sugieres al recolector de basura que haga su trabajo, pero el modo forzado, como su nombre indica, forzaba a una recolección de basura. Así que si llamamos a esa función así, GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced) estamos obligando a una recolección de basura completa en nuestra aplicación.

Ahora bien porque nunca se debería de llamar a esta función así, porque básicamente si nos encontramos en una situación en donde la memoria de nuestro proceso sube en todos los escenario y el recolector de basura en su modo de funcionamiento normal no es capaz de bajar la memoria de uso del proceso, nos encontramos entonces ante un escenario de pérdida de memoria (leaking) y no en una situación donde el recolector de basura no es capaz de hacer su trabajo. Porque os puedo asegurar que en el 99.99% de las veces en que he visto una aplicación que hacia llamadas al recolector de basura para intentar liberar memoria era porque el proceso en sí tenía problemas de pérdida de memoria, no porque el recolector no fuera capaz por si solo de hacer su trabajo bien.

Graficas de memoria en forma de montañas

Llegado a este punto mucha gente se imaginará que la ejecución de las aplicaciones es matemáticamente siempre la misma, es decir, que si en un punto de mi aplicación genero 40mb de memoria y después termino de usar esos objetos automáticamente tengo que ver como mi proceso baja esos 40mb de manera discreta. Pues malas noticias para todos, los sistemas de gestión de memoria sin increíblemente complejos y más para una aplicación de .NET.

Puede que el CLR haya decidido por un casual no liberar esa memoria nativa porque ya que la tiene reservada y no tiene que devolverla al S.O. y más adelante la podrá usar sin tener que volver a pedir memoria a Windows. En ese sentido el CLR también utiliza las funciones de memoria virtual de Windows (VirtualAlloc, VirtualFree, VirtualQuery, VirtualProtect) así que en ese sentido también Windows hace un uso de la memoria de manera conservativa es decir, que no por generar 40mb y ejecutar el recolector de basura automáticamente se liberan 40mb.

Así si por ejemplo el sistema necesita liberar páginas de memoria porque otro proceso, a parte del nuestro en .NET, está haciendo un uso de memoria virtual (no una gran reserva de memoria virtual, acordarse de lo que es una falta de página y como Windows gestiona la memoria de manera perezosa) en ese momento puede decidir recuperar las páginas de memoria de nuestro proceso y entonces nuestra memoria bajará.

Así que simplificar un sistema de gestión de memoria virtual de Windows más el sistema de recolección de basura del CRL de .NET de esa manera es desde mi punto de vista barbaridad. Si queremos saber cuál es el estado de nuestro proceso podemos consultar los contadores de rendimiento para .NET o podemos usar las columnas de Private Bytes, Working Set y Virtual Size en Process Explorer o el comando !address –summary en WinDBG para saber exactamente esa memoria privada en que se gasta en heap, images, ect.

¿Qué son los objetos pineados en memoria?

Cuando hablamos de recolección de basura, también hablamos de compactación de la memoria y para que esa compactación de la memoria pueda ocurrir es necesario mover los datos de direcciones de memoria. En un mundo ideal donde las aplicaciones de .NET sean completamente administradas, es decir 100% .NET sin llamadas a Windows esto debería de bastar, pero el caso es que desde .NET podemos hacer llamadas a componentes no administrados que están fuera del paraguas del recolector de basura.

Imaginaros por un momento que estáis haciendo una llamada nativa a una función hecha en C++ que os pide una dirección de memoria donde vive un objeto que él internamente va a utilizar para realizar su trabajo, resulta que como parte de su trabajo ese componente de C++ tiene un temporizador que cada 30sg comprueba una serie de parámetros en ese objeto en contra de un componente de Windows, pero resulta que entre timer y timer, se lanza una recolección de basura y justo el objeto que este componente de C++ utilizaba (porque recordad que le hemos pasado la dirección de memoria, donde vive) es movida por el recolector de basura en una recolección. Justo cuando el siguiente timer se lance y nuestro componente en C++ vaya a leer la memoria se encontrará con que su memoria ahora mismo está ocupada por otro componente.

Este es solo un ejemplo de que a veces no interesa que determinados objetos de .NET estén siempre en la misma dirección de memoria, cualquiera podría haber sugerido que hagamos una copia del objeto y se la pasemos a C++, pero recordad que siempre pasamos referencias y no copias de objetos.

Es por eso en .NET podemos pinear objetos en la memoria, que como su nombre indica, permite que podamos indicarle al recolector de basura que en ningún caso mueva de dirección de memoria este objeto.

A través de una structura llamada GCHandle podemos pinear objetos así: System.Runtime.InteropServices.GCHandle.Alloc(new object,System.Runtime.InteropServices.GCHandleType.Pinned). Esta llamada nos devuelve un objeto de tipo GCHandle en el que podemos consultar el IntPtr del objeto, si esta inicializado y podemos liberarlo.

Que son las referencias débiles y que es la resurrección de objetos zombies

Como hemos dicho anteriormente cada vez que hacemos una asignación estamos copiando la referencia en memoria de un objeto, es decir, estamos haciendo una referencia fuerte de un objeto. Si existe una referencia fuerte es porque existe una referencia débil, que siguiendo la analogía tiene que ser una referencia que el recolector de basura no tenga en cuenta para evaluar si un objeto es referenciado por otro.

Para poder usar esas referencias débiles tenemos una clase en .NET llamada WeakReference que como su nombre indica nos permite generar esas referencias débiles. Esta clase tiene varias propiedades interesantes como: IsAlive que nos permite consultar si el objeto al que apuntamos sigue vivo; Target que es una referencia (débil) del objeto y TrackRessurection que nos permite hacer tracking de la resurrección de un objeto.

Pero, ¿Qué es exactamente la resurrección de un objeto?

Cada vez que un objeto es eliminado del grafo de referencias de una aplicación, este pasa a una cola llamada la cola de finalización (comando de WinDBG !finlizaqueue) en la que se le da una última oportunidad de ejecutar el código que tenga en el destructor o en el método Dispose (si implementa IDisposable). En ese instante en el que el objeto está en la cola de finalización un objeto está fuera del grafo de objetos, pero si durante la ejecución de ese código se referencia a si mismo de otro objeto que está en el grafo de objetos de la aplicación a través de un objeto estático, diremos que el objeto ha sufrido una resurrección puesto que el recolector de basura sacará a ese objeto de la cola de finalización y el objeto será de nuevo referenciable y volverá a la vida (metafóricamente hablando).

¿Por qué el .NET tiene una finalización no determinista?

Si durante el desarrollo de nuestras clases implementamos IDisposable o creamos un destructor para nuestra clase, el CLR no nos puede asegurar que el destructor de nuestra clase será siempre ejecutado. ¿Por qué?. En ese sentido tenemos que recordar que el CLR es un runtime ejecutado dentro de un proceso y puede que el proceso o el dominio de aplicación se descargue de manera inesperada y en ese sentido el CRL no puede esperar a que todo el código que tengamos en nuestros destructores se ejecute. Por eso se dice que .NET no es determinista en cuanto a la finalización de objetos, porque solamente en el caso de que el dominio de aplicación se descargue o la aplicación se cierre el CLR no nos asegurará que nuestros destructores se ejecuten.

Pero qué pasa si necesito por contrato que el destructor de mi clase se ejecute, o dicho de otra manera, que pasa con las clases que representan recursos del sistema que tienen que ser liberados si o sí.

En este sentido podemos heredad de la clase System.Runtime.ConstrainedExecution.CriticalFinalizerObject haciendo así que el CLR nos asegure que SIEMPRE se ejecutará el destructor de la clase. Como ejemplo diremos que la clase Thread, ReaderWriterLock, SafeHandle y demás heredan de esta clase.

Conclusiones

A lo largo de este articulo hemos repasado los básicos de la gestión de memoria por parte del CLR, y hemos visto que es un sistema extraordinariamente complejo para poder simplificarlo de la manera que lo hacen algunos así que en ese sentido paciencia con la memoria y si tenéis algún problema no dudéis con contactar con el equipo DOT (Debugging and Optimization Team) de Plain Concepts que estaremos encantados de buscar problemas de memoria y rendimiento en vuestras aplicaciones.

Luis Guerrero.

TPL – Cancelacion de Task

¡Hola a todos! Seguimos con los post sobre Task Parallel Library de .NET Framework 4, en este artículo vamos a ver cuál es el soporte de cancelación de Task que tenemos en TPL.

Como hemos comentado en el artículo anterior las Task son la unidad mínima de ejecución de TPL, incluso PLINQ (Parallel LINQ) utiliza Task internamente para sus operaciones. También hemos visto como las Task tienen alguna similitud con los Worker Threads del ThreadPool de .NET.

Cuando nosotros en código lanzamos un Worker Thread con el método ThreadPool.QueueWorkItem, estamos encolando un trabajo para que se ejecute en otro Thread diferente, pero no tenemos control en como este Worker thread se ejecuta, de hecho no podemos saber cuándo se ha completado, no podemos cancelarlo y no podemos devolver un resultado de esa ejecución asíncrona.

Esto ahora con las Task cambia, ahora a través de la propiedad Status podemos saber en qué estado está nuestra Task, la propiedad Result nos dará el resultado de la ejecución de la Task. Pero, ¿Qué hay de la cancelación?.

Cancelar un Task no es una tarea trivial, porque implica muchas decisiones y puede conllevar muchos errores. Si pensamos en cómo se cancela un Thread, el CLR lo que hace es inyectar un ThreadAbortException en la ejecución del thread haciendo que este se aborte, pero lo que no podemos controlar es en qué punto de la ejecución del thread se va a producir esto, y lo más peligroso de eso es que podemos estar dentro de un bloque de lock y la inyectarse la excepción puede pasar que nunca se salga de ese bloque de lock, haciendo que pueda pasar un deadlock en nuestro código. Este es solo un ejemplo de lo que puede pasar pero por supuesto puede ser mucho más complicado.

¿Cómo se puede cancelar una Tarea de manera segura?

Hay dos opciones posibles, si la tarea esta creada y se ha llamado al método Start y la Task está esperando para ejecutarse (Status=WaitToRun) y se cancela TPL automáticamente cancelará la tarea por nosotros (Status=Cancelled). Pero si la tarea está ejecutándose o esperando a un evento externo se realiza de otra manera.

Para poder ver esta segunda parte tenemos que comprender primero como se gestiona esto en código para entender cuál es el mecanismo que TPL nos proporciona para cancelar Task.

Task t = new Task((index) =>
{
    Thread.SpinWait(1000);
    if (!cancellationSource.IsCancellationRequested)
    {
        Thread.Sleep(r.Next(0, 1000));
    }
}, i, cancellationSource.Token);
list.Add(t);

Si nos fijamos en este ejemplo de código al crear una instancia de la clase Task ahora pasamos por parámetro un CancellationToken que nos va a permitir comunicarnos con el sistema de cancelación de TPL. Ese CancellationToken viene de una estructura llamada CancellationTokenSource que es realmente la que gestiona la comunicación de la cancelación de las Task.

Lo primero que hacemos es generar una lista con todas las Task que vamos a ejecutar, con eso lo que tenemos es instancias de la clase Task, pero todavía no se están ejecutando, es después cuando llamamos al método Start de todos los objetos Task a la vez.

Como el TaskScheduler predeterminado es el ThreadPool de .NET al principio en una carga de trabajo tan grande 10000 Task, el ThreadPool se tendrá que tunear a sí mismo para empezar a crear Workers Threads y aumentar dinámicamente la capacidad de ejecución por nosotros. Esta es una de las cosas más cómodas de TPL, porque nosotros no tememos que directamente estar pendiente de la heurística del ThreadPool para saber cuál es el número de Threads ideal para nuestro sistema.

Además de todo esto tenemos un método que nos permite cancelar todas las Task a la vez.

public void CancelTask()
{
    cancellationSource.Cancel();
}

Simplemente lo que hacemos es llamar a Cancel del objeto CancellationTokenRequest y este se comunicará con la infraestructura de TPL para empezar la cancelación. Como dijimos antes la cancelación de tareas que están es estado WaitingToRun es automática, pero las que se están ejecutando es más complicado.

Si nos fijamos en el ejemplo de arriba, en el cuerpo del método que se ejecutará por cada Task, hay una sentencia condicional if !cancellationSource.IsCancellationRequested en la que estamos comprobando que no se ha realizado una solicitud de cancelación de tareas. Esto es así porque es la única manera segura y organizada de cancelar un Task. Este ejemplo no es muy representativo para el soporte de cancelación, pero por ejemplo si en esta Task se estuvieran generando por pasos un informe que tarda mucho tiempo, generar el reporte, consulta a la base de datos, ect, podemos hacer comprobaciones en cada paso de que se ha solicitado una cancelación de la Task y así de manera segura cancelar la petición y liberar todos los recursos generados por ese reporte.

Para poder sacar todo el partido de la cancelación en nuestras Task, tenemos que trabajar activamente con TPL para que la experiencia de cancelación sea la más adecuada a cada situación, habrá pasos que no se puedan cancelar hasta un determinado punto, en cualquier caso TPL nunca abortará la ejecución de ese Task por nosotros, pues como he comentado antes esto puede conllevar muchos problemas.

image

Otro de los escenarios posibles cuando se trabaja con cancelación es que de alguna manera nos gustaría saber cuándo un CancellationToken se ha cancelado, para eso tenemos varias opciones, porque el propio CancellationToken tiene un método para que registremos un delegado de tipo Action que será llamado cuando se cancele el CancellationToken. También existe la opción de registrar un objeto de tipo ICancelableOperation, que es la interfaz que implementan el tipo CancellationTokenSource para poder enlazar así objetos de cancelación y crear dependencias entre ellos.

Aquí os podéis descargar el código de ejemplo de la aplicación de WPF.

http://www.luisguerrero.net/downloads/TaskCancel.zip

Saludos. Luis.

TPL – Task

Como comentamos en el anterior artículo las Task son las unidades básicas de ejecución dentro de TPL (Task Parallel Library) y en este doble artículo vamos a ver cuáles son las posibilidades que tenemos para trabajar con las Task dentro de nuestro código.

Una de las misiones de la TPL es ofrece una API consistente para el trabajo concurrente de software, es decir para tareas que se van a ejecutar de manera concurrente en un sistema con más de un procesador. Como bien es sabido la unidad mínima que el SO es cada de enviar para ejecutar es un Thread, pero nosotros aquí estamos hablando de Task. ¿Qué relación hay entre un Thread y un Task?.

Un thread es una unidad mínima y demasiado concreta para ejecutar código de manera concurrente. Dentro de .NET se puede crear una instancia de la clase Thread para ejecutar un método que nosotros queramos dentro de un thread diferente, pero una vez que ese método se ejecuta el Thread termina y se liberan los recursos utilizados por él. Es por eso que utilizar una estructura un poco más de alto nivel nos ayuda a abstraernos de cómo nuestras Task se ejecutan.

Ahora bien esto no quiere decir que las Task *no* se e ejecutan dentro de un thread, sino que también se ha creado una estructura intermedia llamada TaskScheduler que nos permite definir cómo se van a ejecutar nuestras Task. La igualdad con respecto al par Schedule/Thread es muy parecida pues tenemos conceptos muy similares, pero como veremos más adelante las Task son mucho más flexibles que un Thread y permite un sinfín de configuraciones, es más permiten que se ejecuten de manera síncrona, cosa que un thread no puede.

Basic – Creación de una Task.

Para crear una instancia de la clase Task podemos hacerla de varias maneras, aquí tenemos algunos ejemplos.

Task t = new Task(() =>
{
    Console.WriteLine("hola desde un task");
    Thread.Sleep(1000 * 4);
});

Task argumento = new Task(index =>
{
    int value = (int)index;
    for (int i = 0; i < value; i++)
    {
        DoStuff();
    }
    Thread.Sleep(1000 * 4);
}, 90);

En ambos ejemplo se han utilizado Lambdas para crear los delegados que ejecutarán el código pero se puede utilizar el delegado Action y Action<T> para sacar ese valor a un método externo.

Con esto simplemente lo que hemos hecho es definir únicamente el objeto Task y ahora mismo esta simplemente creado pero no se le ha especificado que se tiene que ejecutar. Para ejecutarlo tenemos dos opciones, de manera síncrona y de manera asíncrona (concurrente). Puede parecer algo raro el tener el soporte de ejecución síncrona el algo que está pensado para ejecutarse de manera concurrente siempre, pero es que en algunos casos es útil y así podemos definir todo nuestro trabajo con Task y luego decidir cómo queremos ejecutar.

t.RunSynchronously();            

t.Start();    

En el método Start tenemos una sobrecarga que nos permite especificar cuál es el TaskScheduler en el que queremos ejecutar nuestra Task. Si no especificamos ninguno el sistema automáticamente utiliza un TaskScheduler que utiliza internamente el ThreadPool de Windows para ejecutar el código. Por supuesto podemos definir y crear nuestros propios TaskScheduler simplemente heredando de la clase TaskScheduler. Además del que está definido con el ThreadPool si estamos dentro de una aplicación de UI como Windows Forms o WPF estos disponen de un TaskScheduler propio para ejecutar tareas dentro del bucle de mensajes de la aplicación.

Para poder acceder a este TaskScheduler tenemos que llamar a este código:

TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();

Además de estas opciones tenemos algunas otras con las que podemos trabajar con Task. Podemos esperar la ejecución de una Task concreta un tiempo determinado o simplemente podemos esperar infinitamente hasta que la tarea termine.

Una de las sobrecargas del método wait acepta un objeto de tipo CancellationToken que como su nombre indica es un objeto que nos ayuda a tener un soporte de cancelación de Task que veremos más adelante.

También disponemos de varias propiedades dentro de la clase Task que nos dan información del estado de la tarea como:

  • State: enumerado que nos indica en qué estado está la Task, como: Created, WaitToRun, Running, RanToCompletion, ect.
  • AsynState: objeto de usuario.
  • Exception: si se ha producido una excepción durante la ejecución de la Task y no se ha controlado el estado de la Task será Faulted y en esta propiedad aparecerá un objeto de tipo AggregateException que contiene una lista de todas las excepciones que se han producido durante la ejecución de la Task.
  • Result: obtiene de manera segura el resultado de la ejecución de la Task.

Como última opción veremos que tenemos un método llamado ContinueWith que nos permite ejecutar la Task especificada justo después de que esta termine pudiendo así enlazar Task y crear dependencias entre ellas. Esto es muy útil cuando se trabaja con funciones que van a tardar mucho en ejecutarse como una lectura de un fichero, una query en una base de datos o una actividad en background.

Con lo que dijimos antes vamos a ver un ejemplo utilizando TaskScheduler.FromCurrentSynchronizationContext() en una aplicación WPF para ver ContinuwWith.

Como bien es sabido en WPF y en Windows Forms no es posible actualizar el estado de un objeto de UI desde el Thread que no es el Thread que creo el objeto, es decir si estamos ejecutando código desde otro Thread, algo normal con las Task, no vamos a poder actualizar el resultado de nuestra Task, así que tenemos que sincronizar el acceso.

Task.Factory.StartNew(() =>
{
    // simulando una operacion lenta

    Thread.Sleep(1000 * 2);
    return "hola";

}).ContinueWith(task =>
{
    result.Text = task.Result;

}, TaskScheduler.FromCurrentSynchronizationContext());

Aquí está el disponible el código de ejemplo.

http://www.luisguerrero.net/downloads/task101.zip

 

Saludos. Luis.

Task Parallel Library – Introduccion

Una de las nuevas novedades que .NET Framework 4.0 incluye es el la Task Parallel Library una serie de APIS nuevas para la programación multihilo. La idea principal de esta librería, que viene incluida en el propio framework, es que cuando tengamos que añadir paralelismo y concurrencia a nuestras aplicaciones sea de lo más sencillo.

Actualmente los procesadores ya no incrementan la velocidad en Gigahercios sino que lo que hacen es replicar el hardware haciendo que nos encontremos dentro del mismo encapsulado FPGA dos procesadores exactamente iguales con sus caches de segundo y primer nivel. Esto cambia la manera de desarrollar software porque ya no nos encontramos con procesadores más rápidos sino con procesadores con más cores, 2,4,6,8 ect.

Con este nuevo escenario tenemos los desarrolladores tenemos que empezar a paralelizar nuestro software para explotar toda la potencia de los procesadores.

Una de las cosas buenas de la TPL, es que si la maquina donde se va a ejecutar tiene 2, 4 o 8 procesadores, la TPL es capaz de escalar sin necesidad de recompilar ni configurar, es decir es capaz de usar todos los cores disponibles.

Eso quiere decir que cuanto más cores utilicemos más velocidad ganaremos, aunque esto no es siempre así, porque no todas las tareas son sensibles de ser paralelizadas. Además hay que tener en cuenta que usar la TPL añade complejidad en la ejecución de la aplicación y esto en algunas ocasiones puede hacer que se degrade el rendimiento y no lo aumente. Hablaremos de eso más adelante.

Aquí tenemos la primera toma de contacto con la TPL:

Parallel.For(startIndex, endIndex, (currentIndex) => DoSomeWork(currentIndex));

Parallel.For nos permite ejecutar de manera concurrente el cuerpo de un bucle for, haciendo que la ejecución se propague por todos los cores disponibles en el sistema. Así de sencillo.

Pero como hemos comentado antes no esto a veces aumenta el rendimiento y en otros lo degrada. Veamos porque.

Cuando realizamos un bucle normal todo el código se ejecuta de manera secuencial, es decir una instrucción detrás de otra, pero cuando estamos realizando una paralelizacion de nuestro código tenemos varios threads que están ejecutando código en el mismo instante de tiempo. Si nos ponemos a pensar cómo se podría implementar a mano un Parallel.For, lo primero que tendríamos que hacer es realizar una partición de las iteraciones para dependiendo de los procesadores que tengamos repartir el trabajo.

Si tenemos por ejemplo que recorrer una lista de 50000 elementos y tenemos 2 procesadores, podemos partir la lista de dos sublistas de 25000 y generar dos threas que se encarguen de recorrer esos elementos. Ponemos los threads a ejecutar y tenemos que sincronizar cuando los dos threads terminan.

Básicamente esto es de lo que se encarga el Parallel.For de hacer por nosotros, de una manera cómoda y elegante.

Ahora bien, ¿Cuándo no es recomendable realizar un Parallel.For?, hay una regla que funciona en la mayoría de los casos, cuando el tiempo de ejecución del cuerpo del for sea mayor o igual que el tiempo de creación de los threas y de la sincronización, también es lo mismo si tenemos colecciones pequeñas. ¿Qué quieres decir esto?, veamos un ejemplo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ParalleFor
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program();
        }

        private List<int> GetRandomList()
        {
            List<int> list = new List<int>();
            for (int i = 0; i < 90000000; i++)
            {
                list.Add(i);
            }
            return list;
        }

        public Program()
        {
            List<int> list = GetRandomList();

            Stopwatch st = new Stopwatch();

            // sum all
            decimal value = 2;
            st.Start();
            for (int i = 0; i < list.Count; i++)
            {
                decimal final = value * list[i];
            }
            st.Stop();
            Console.WriteLine(st.Elapsed);
            st.Reset();

            st.Start();
            Parallel.For(0, list.Count, index =>
            {
                decimal final = value * list[(int)index];
            });
            st.Stop();
            Console.WriteLine(st.Elapsed);
        }
    }
}

Si nos fijamos la lista de con la que trabajamos es realmente grande, eso significa que si paralelizamos el bucle for ganaremos en rendimiento como muestra la salida de la ejecución. Pero si tenemos una lista pequeña no ganaremos tiempo sino que perderemos porque tenemos que sincronizar los n threas para esperar a que todos terminen, además del tiempo necesario para inicializarlos.

Otro de los grandes problemas de la paralelizacion es que necesitamos estructuras para sincronizar nuestro código y tener claro los conceptos de: locks, deadlocks, race condition (data and flow), etc.

En el siguiente artículo veremos el soporte para Task, las unidades básicas de ejecución dentro de TPL.

Aquí tenéis el código fuente del ejemplo.

http://www.luisguerrero.net/downloads/parallefor.zip

 

Saludos. Luis.

Firefox 3.5 : Multithreading javascript code

Ya ha salido Firefox 3.5… pero yo solamente voy a hablar de una característica que han introducido la gente de Mozilla que es realmente interesante: Web Workers Threads

La utilización de java script en los navegadores se ha disparado, todo se hace hoy en día con javascript y eso ha tenido como consecuencia que la gente que se dedica a hacer los navegadores mejore cada día la velocidad con la que se ejecute el código. Pero aunque tenemos muchos tipos de runtimes para ejecutar javascript V8, TraceMonkey, IE todos tienen una peculiaridad y es que se ejecuta el código síncronamente, no permitiendo que se paralelice la ejecución de código.

Pero ese día ha acabado, la gente de Firefox consciente de que se necesita incrementar la velocidad de javascript ha decidido incluir esta característica en Firefox 3.5.

La idea es simple:

var myWorker = new Worker('my_worker.js');
myWorker.onmessage = function(event) {
  print("Called back by the worker!\n");
};

Aquí podemos ver un ejemplo de código para generar ese worker thread, que podemos encontrar en la wiki de desarrollo de Firefox https://developer.mozilla.org/En/Using_DOM_workers

A mí personalmente no me gusta nada Javascript ni html, es todo muy tedioso y uno tiene que aprender a hacer muchos hacks para hacer cualquier cosa incluso para centrar una imagen en la pantalla, pero claro es mi opinión.

Luis.

Tool: Memory Pressure

Esta sencilla herramienta permite generar presión en la memoria del sistema a nuestro gusto.

clip_image002

La interfaz de usuario es muy sencilla, podemos seleccionar la cantidad de Megabytes que queremos reservar y cuál es el tamaño de los bloques que queremos usar. Hay que tener en cuenta que esta aplicación utiliza la reserva de memoria del heap de Windows, es decir llama a Marshal.AllocHGlobal que a su vez llama a LocalAlloc.

Una vez que tenemos la memoria reservada podemos liberarla para poder eliminar la presión.

Durante la reserva de memoria se llama a GC.AddMemoryPressure que notifica al recolector de basura la presión actual que se está haciendo en memoria nativa.

Os podeis instalar este software desde: http://www.luisguerrero.net/applications/MemoryPressure/

Tambien os podeis descargar el código fuente: http://www.luisguerrero.net/downloads/MemoryPressure.zip

Luis.

Rendimiento para el modelado de clases

Rendimiento. En muchos proyectos en los que trabajo una de las preocupaciones a la hora de hacer el proyecto es el rendimiento de la aplicación

Una de las tareas, por no decir la única, es trabajar con datos en una aplicación, modelamos constantemente clases que tiene estado y a su vez exponen una serie de métodos para que los podamos invocar. Hoy a lo que me voy a dedicar a explicar es justamente a ese modelado de datos, al estado de nuestras clases.

Dentro de .NET Framework tenemos varias maneras de definir el estado de una clase:

  • Field (Campo)
  • Property (Propiedad)
  • DependencyProperty (Propiedades de Dependencia)

Estas últimas están pensadas para la interacción de esas clases con Interfaz de Usuario (UI).

Las propiedades son una normalización de los métodos de acceso (set) o de obtención (get) que normalmente se hacía para obtener el acceso a los campos o modificarlos. Realmente las variables se guardan en los campos (field) solo que las propiedades permiten normalizar el acceso y encapsular su funcionalidad de acceso. Son implementadas como métodos dentro del ensamblado.

Lo que vamos a ver en este post es cual la velocidad de acceso a estas propiedades desde .net para saber cuál es la mejor opción si estamos trabajando con el estado de las propiedades y campos. Cualquiera me podría decir que según la lista anterior está claro que siempre la mejor opción (desde el punto de vista del rendimiento) son los campos (Field) puesto que accederos directamente y sin intermediarios, pero además de eso cuando los proyectos empiezan a complicarse puede pensar en usar la reflexión para acceder a los datos, porque se quiere automatizar el acceso a las propiedades de las clases.

Así que lo que vamos a hacer es tener dos tipos de clases una con campos y propiedades, (Field y Property), otra con DependencyProperty y vamos a medir el tiempo que tardamos en acceder a esas variables. Una de las desventajas de usar DP es que para poder usar su funcionalidad tenemos que heredar de la clase DependencyObject, que es la clase que implementa la funcionalidad de DP. Además como he comentado anteriormente las DP únicamente se usar para definir las propiedades de los controles de interfaz de usuario y de los objetos de negocio que interactúan con la UI.

Lo que vamos a hacer son una serie de pruebas con una clase intermedia para medir el tiempo y que se encargue de ejecutar el bloque de código n veces y de medir el tiempo que tarda en hacerlo.

Para la medida del tiempo no vamos a usar DateTime.Now antes de empezar y después de ejecutar porque la medida de tiempo de DateTime.Now no es de alta resolución y no vamos a tener la precisión que necesitamos para medir diferencias de tiempo. En vez de DateTime.Now vamos a usar Stopwatch, clase que está en System.Diagnosis que nos permite hacer medidas de tiempo en alta resolución, el uso de esta clase es bastante sencilla así que el lector la podrá descubrir por el mismo.

Vamos al lio, tenemos está clase normal de C#

public class NormalType
{
    internal int number;

    public int Number
    {
        get { return number; }
        set { number = value; }
    }

    internal string _string;

    public string String
    {
        get { return _string; }
        set { _string = value; }
    }
    internal Item item;

    public Item Item
    {
        get { return item; }
        set { item = value; }
    }
}

Como podéis ver es una clase con tres propiedades un entero, un string y un tipo referencia complejo (Item), todas las propiedades tienen un campo que respalda el valor, así que realmente las propiedades simplemente exponen los valores de los campos sin más.

public class DependencyPropertyType : DependencyObject
{
    public int Number
    {
        get { return (int)GetValue(NumberProperty); }
        set { SetValue(NumberProperty, value); }
    }

    public static readonly DependencyProperty NumberProperty =
        DependencyProperty.Register("Number", typeof(int), typeof(DependencyPropertyType), new PropertyMetadata(0));

    public string String
    {
        get { return (string)GetValue(StringProperty); }
        set { SetValue(StringProperty, value); }
    }

    public static readonly DependencyProperty StringProperty =
        DependencyProperty.Register("String", typeof(string), typeof(DependencyPropertyType), new PropertyMetadata(null));

    public Item Item
    {
        get { return (Item)GetValue(ItemProperty); }
        set { SetValue(ItemProperty, value); }
    }

    public static readonly DependencyProperty ItemProperty =
        DependencyProperty.Register("Item", typeof(Item), typeof(DependencyPropertyType), new PropertyMetadata(null));

}

Esta otra clase es la clase que implementa las DependencyProperty, como veis ahora por cada propiedad hay una definición de una clase de tipo DependencyProperty que es estático y de solo lectura (static readonly) y además no es el tipo que definimos las propiedades. Eso que significa que los valores de esta clase son compartidos, desde luego no porque las propiedades son de instancia no estáticas, solo que ahora utilizamos dos métodos helpers que nos ayudan a acceder y establecer los valores de las propiedades (GetValue,SetValue). Esos dos métodos están definidos en la clase base DependencyObject. Realmente nosotros no tenemos información de donde se están guardando esas variables dentro de nuestra clase no hay ningún campo (Field) ni lista ni nada que nos indique donde se están almacenando esos valores, y si miramos en la clase base nos pasa lo mismo. En otro post desvelare toda la potencia y misterios de las DP ahora simplemente lo usaremos sin más para esta prueba de rendimiento.

Ahora lo que vamos a hacer son las pruebas en sí de acceso a estos valores.

image 

Sin reflexion

Con Reflexion

Getter Field

92,176

4483,588

Getter Property

142,813

3169,277

Getter Dependency Property

755,433

1619,854

Setter Field

0,607

3716,121

Setter Property

0,584

2600,364

Setter Dependency Property

129,623

973,799

La prueba está dividida en dos grandes grupos Setters y Getters (establecer y obtener).

Para los Setters (establecer) los campos tiene el mejor rendimiento cuando se trabaja con ellos, pero son los que tiene el peor rendimiento cuando se trata de acceder a través de reflexión, las propiedades es el mismo caso solo que se mejora un poco el tiempo de acceso desde reflexión y para las Dependency Property están muy equilibradas en cuanto a tiempo de acceso con y sin reflexión.

En el caso de Getters (obtener) la historia se repite, los campos y propiedades son increíblemente rápidos, pero cuando se trabaja con ellos a través de reflexión se penaliza, las Dependency Property en relación con el Setter son más rápidas con y sin reflexión.

El caso especial de las Dependency Proeprty.

¿Por qué las Dependency Properties tiene mejores tiempos?, podríamos decir que la prueba esta adulterada, porque realmente siempre se utiliza los métodos GetValue y SetValue que DependencyObject nos proporciona para acceder a esos valores y ahí es donde está la sobrecarga pero hay que tener en cuenta que la reflexión usada en esta prueba es solo para acceder a las instancias de las DependencyProperty que son necesarias en las llamadas de GetValue y SetValue.

Por si sentís curiosidad los valores de las instancias de clases que heredan de DependencyObject se guardan en un campo interno de la clase DependencyObject llamado _effectiveValues de tipo EffectiveValueEntry,

private EffectiveValueEntry[] _effectiveValues;

EffectiveValueEntry es la clase que manera el valor de una Dependency Property, cada una de estos EffectiveValueEntry tiene asociado un índice dentro de todos los registros de Dependency Proeprty al cual nosotros podemos acceder desde DependencyProperty.GlobalIndex (int) pero no podemos usar de ninguna manera, no sé porque Microsoft ha permitido acceder a esta propiedad si no tiene utilizad.

La solución de Visual Studio para que hagáis vuestras pruebas.

http://www.luisguerrero.net/downloads/DPPerformance.zip

Luis Guerrero.