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

MySQL Server Performance on Microsoft Azure, a lesson learned from a Linux Distribution

Microsoft Azure support infrastructure as a service, that is, support for creating your custom virtual machines with the operating system of your choice. So you can chose Linux as your operating system. Linux has a huge list of different distributions to choose from. In this post I want to show the different in performance found when you host a MySQL Server on different Linux distributions.

Supported and no supported Linux distributions

Inside the Azure portal you can create virtual machines from the gallery. Those images include Windows Server images and Linux. For Linux there are Ubuntu, Centos and OpenSuse. Microsoft support is limited to the Azure platform and services and a support case through support agreement is required to engage the Azure support team. Microsoft support will not offer assistance in the design, architecture, development, or deployment of applications or solutions on Azure. If Linux support is needed, customers should reach out to the vendors directly for support. For more info click here.

Also you can download to your Azure Storage images made by the community, by selecting Virtual Machines on left side, and on that panel, selecting “Images” tab and there clicking on ”Browse VM Depot”.

clip_image002

There you can select a wide range of Linux distribution with a lot flavors, that is. You can download an image with all necessary software to run WordPress, Alfresco, ect.

On the list there are a Debian GNU/Linux 7.0 wheezy that we are going to use on our demo too. That is because when want to show the different between a Microsoft’s supported Linux distribution and a community Linux distribution.

Mysqlslap

Mysqlslap is a command line tool that is included on the mysql-client package that helps database administrator to simulate work load on the server. This tool can simulate multiple concurrent users, multiple queries and can work with string and int types while simulating the work load.

This tool is great for a performance comparison because you can create work load on every machine and using the same tool.

A typical command line for this tool could it be like this.

mysqlslap --user=root -p --auto-generate-sql --concurrency=1 --number-of-queries=100 --number-char-cols=8 --number-int-cols=10 
--iterations=10

During this load test I’m going to change the currency value to simulate more users on the test, this will help to measure the performance on the Linux distributions.

All test are going to be executed on machines that doesn’t have swap partition, only have one disk for the system, the Virtual Machine Size is A3 (4 cores, 7GB of memory).

IOPS

All virtual machine have a limit on the number of IOPS (Input/Ouput Operation per Seconds) of 300 IOPS for basic virtual machines and 500 IOPS for Standard virtual machines.

To increase this number you can add more disk to your machine and set them as a RAID0 to enable striping. With this level there is no redundancy and no level of fault tolerance. The more disk you add to the virtual machine the more IPOS per second you can have.

Results

Concurrency

Debian 7.0

Ubuntu 14.10

CentOS 13

Debian 7.0

Ubuntu 14.10

CentOS13

1

1.991

1.9740

0.0470

2.24

2.179

0.069

10

0.442

0.3670

0.0240

0.393

0.461

0.049

100

0.098

0.0930

0.0250

0.145

0.138

0.051

500

9.778

9.5290

8.0280

14.249

13.353

12.546

clip_image004

Conclusion

The best Linux distribution for MySQL performance is CenOS, it has the best time executing all queries, and obviously the standard tier is the best option too.

All machines on this series of test doesn’t have a swap partition, so that mean that there is no noise on the hard drive, affecting performance.

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