domingo, mayo 10, 2009

Primer día en Windows 7

Bueno, finalmente instale Windows 7, lo único que puedo decir es que es altamente recomendable. A continuación hago un resumen de mi experiencia (todo esto lo hice en mi portatil Lenovo ThinkPad X61 2GB RAM)

Preparación

Antes de iniciar obviamente obtuve un backup pero a falta de uno hice tres: solo archivos, imagen de disco, windows easy transfer. El último lo hice para ver si me aceleraba el proceso de resintalación.

Instalación base

Basado en esperiencias anteriores habia destinado un par de horas para realizar la instalación básica, mi sorpresa fue enorme cuando 18 minutos después de prender la máquina para instalar Windows 7 estaba listo para instalar otras aplicaciones.

Lo primero que hice fue agregar todos los componentes que no se instalan por defecto (IIS, MSMQ, etc) y nuevamente me sorprendi pues la activación tomo 2 minutos.

Instalación Aplicativos

Lo siguiente a instalar es Office 2007 que igualmente esperaba tomará unos cuantos minutos por lo que inicie la instalación y me fui a hacer otras cosas, 8 minutos después volvi a buscar algo y vi que la instalación estaba completa.

Emocionado por el éxito decidí reinstalar todo pero tenía cosas para hacer (la vida normal que se interpone) así que durante la tarde finalmente pude sentarme a continuar con la instalación.

En general el proceso de instalación consistía en revisar la última versión de la aplicación, descarga (si es necesario), instalación. Para darles una idea de lo que esto significa listo las aplicaciones instaladas

Esto tomo unas 6 horas lo que me hace pensar que la instalación es mucho más rápida pues recuerdo que en otras  ocasiones me tomaba mucho más llegar a tener todo instalado.

transferencia de preferencias

Lo primero que intente fue utilizar la imagen de Windows Easy Transfer que habia generado. Windows 7 la proceso en un tiempo que encuentro es un poco excesivo (50 minutos para 17 GB de datos) pero al ser un proceso de una única vez deje que se completará.

Al finalizar la recuperación pude observar, con alegria, que todas mis datos, personalizaciones y configuraciones se recuperaron sin ningún problema con lo que luego de 8 horas tenía todo mi entorno listo en Windows 7.

Adicionales

Una vez que termine la instalación note que había un par de cosas que extrañaba:

  • Utilitarios ThinkPad. Principalmente Access Connections, Presentation Director y Power Manager.
    • Access Connections es un utilitario de Lenovo que permite definir configuraciones de redes (IP, Proxy, impresora, seguridad, etc.) y asociarlo con puertos, MACs para facilitar el cambio de configuración. En el último año ha sido de mucha utilidad. En fin pude instalarlo y funciona, el icono de acceso rápido no se inicia, pero eso no es muy preocupante pues no lo uso habitualmente.
    • Power Manager: Las capacidades de optimización que brinda power manager me han ayudad mucho. Lo bunoe instalado y funcionando :)
    • Presentation Director: La combinación Windows+P en Windows 7 lo elimina de mi lista :)
  • Windows Virtual PC: windows 7 utilizará la nueva versión de Virtual PC por lo que fue necesario que descargue el beta y lo instale. Posteriormente pude abrir mis máquinas virtuales, tengo que decir que aun no estoy muy familiarizado con la nueva interfax y creo que me tomara un tiempo

Toque finales

Obviamente descargar todos mis correos con Outlook, y descargar actualizaciones necesarias. La verdad eso lo deje corriendo durante la noche por lo que no se cuanto tomó pero hoy ya tengo todo  listo.

Primeras apreciaciones

Mis primeras apreciaciones son muy positivas.

  • El sistema tiene una respuesta mucho más adecuada en general
  • El proceso de reinicio es ligeramente más rápido pero el acceso al escritorio es mucho mas inmediato
  • El consumo de memoria es notoriamente menor, con Vista tenía deshabilitado Aero, transparencias, todos los gadgets y varios servicios de presentación y al momento de inicio de Windows (sin aplicaciones) no tenía más de 960 MB libres. En este momento estoy ejecutando outlook, IE, live writer, con todas las ventajas de UI y tengo 1015MB libres.
  • El trabajo del equipo de Windows en el stack de IU es sin duda muy notorio la interacción en general con Windows es mucho mejor

En resumen ha sido una experiencia muy gratificante. Desde hoy empiezo a recomendar la migración a todos :)

domingo, mayo 03, 2009

Alternativas de Bloqueo en .Net

Recientemente pude continuar mi interrumpida lectura de Concurrent Programming on Windows y durante la misma se discuten los distintos tipos de Sincronización que se pueden usar con .Net. Como en casi toda lectura que he visto se menciona el costo adicional que puede incorporar el uso de las clases ReaderWriterLock, ReaderWriterLockSlim frente al uso de Monitor.Enter, Monitor.TryEnter (lock o syncLock), en el libro se hace un análisis del porque el impacto adicional en el rendimiento, pero como siempre me quedaban dudas respecto a cual es el verdadero margen de diferencia.

Existen varias comparaciones del impacto en rendimiento siendo casi siempre el comentario la gran diferencia que hay a favor de Monitor/lock frente a ReaderWriterLock , por ejemplo http://is.gd/wrDH, http://blogs.msdn.com/ricom/archive/2006/05/05/590955.aspx (comentarios, no el post en si mismo) sin embargo siempre he encontrado dichos números un poco vagos (espero no caer en el mismo error)

Usualmente toda tarea de sincronizacion siempre la hago con lock por ser la más fácil de implementar y porque nunca he tenido problemas de contención, sin embargo aprovechando que hoy en la tarde tenía tiempo decidí hacer una prueba muy simple para comparar las alternativas en distintos escenarios.

Componentes

Mi prueba es muy simple tengo 3 clases que exponen dos métodos uno de lectura (Shared Lock) y uno de escritura (Exclusive Lock). Ambos métodos sincronizan el acceso a través de cada uno de los mecanismos a considerar en la prueba, la clase TraditionalLock<T> utiliza el tradicional lock sobre una clase creada en el constructor de la clase, la clase ReaderWriteLock<T> utiliza una instancia de la clase del mismo nombre y finalmente ReaderWriteLockSlim<T> utiliza la clase del framework 3.5.

   1:
   2:     class TraditionalLock<T> : ILockTest<T>
   3:     {
   4:         private T data;
   5:         private object sync = new object();
   6:     
   7:         public T  Read()
   8:         {
   9:             lock (sync)
  10:             {
  11:                 // Thread.Sleep(50);
  12:                 return this.data;
  13:             }
  14:         }
  15:  
  16:         public void  Write(T value)
  17:         {
  18:             lock (sync)
  19:             {
  20:                 // Thread.Sleep(50);
  21:                 this.data = value;
  22:             }
  23:         }
  24:     }
  25:  
  26:     class ReaderWriteLock<T> : ILockTest<T>
  27:     {
  28:         private T data;
  29:         private ReaderWriterLock sync = new ReaderWriterLock();
  30:  
  31:         public T Read()
  32:         {
  33:             sync.AcquireReaderLock(System.Threading.Timeout.Infinite);
  34:             try
  35:             {
  36:                 // Thread.Sleep(50);
  37:                 return this.data;
  38:             }
  39:             finally
  40:             {
  41:                 sync.ReleaseReaderLock();
  42:             }
  43:         }
  44:  
  45:         public void Write(T value)
  46:         {
  47:             sync.AcquireWriterLock(System.Threading.Timeout.Infinite);
  48:             try
  49:             {
  50:                 // Thread.Sleep(50);
  51:                 this.data = value;
  52:             }
  53:             finally
  54:             {
  55:                 sync.ReleaseWriterLock();
  56:             }
  57:         }
  58:     }
  59:  
  60:     class ReaderWriteLockSlim<T> : ILockTest<T>
  61:     {
  62:         private T data;
  63:         private ReaderWriterLockSlim sync = new ReaderWriterLockSlim();
  64:  
  65:         public T Read()
  66:         {
  67:             sync.EnterReadLock();
  68:             try
  69:             {
  70:                 // Thread.Sleep(50);
  71:                 return this.data;
  72:             }
  73:             finally
  74:             {
  75:                 sync.ExitReadLock();
  76:             }
  77:         }
  78:  
  79:         public void Write(T value)
  80:         {
  81:             sync.EnterWriteLock();
  82:             try
  83:             {
  84:                 // Thread.Sleep(50);
  85:                 this.data = value;
  86:             }
  87:             finally
  88:             {
  89:                 sync.ExitWriteLock();
  90:             }
  91:         }
  92:     }
  93:  
  94:     class VolatileLock : ILockTest<int> 
  95:     {
  96:         private volatile int data;
  97:  
  98:         public int Read()
  99:         {
 100:             return this.data;
 101:         }
 102:  
 103:         public void Write(int value)
 104:         {
 105:             this.data = value;
 106:         }
 107:     }

Todas estas clases son utilizadas por un controlador que utiliza el ThreadPool de .Net para solicitar la ejecucion de los métodos de escritura y lectura en una proporción de n:1 que varia desde 1:1 hasta 29:1 para estas pruebas. El tiempo total de la ejecución de todos los métodos es medido utilizando la clase StopWatch


   1: static long MeasureLockTest<T>(ILockTest<T> instance,T value, int totalExecutions, int ratio)
   2: {
   3:     Stopwatch sw = new Stopwatch();
   4:     int writerCounter = 0;
   5:     int readerCounter = 0;
   6:  
   7:     ratio++;
   8:     sw.Start();
   9:     for (int counter = 0; counter < totalExecutions; counter++)
  10:     {
  11:         if (counter % ratio == 0)
  12:         {
  13:             ThreadPool.QueueUserWorkItem((state) => instance.Write(value));
  14:             writerCounter++;
  15:         }
  16:         else
  17:         {
  18:             ThreadPool.QueueUserWorkItem((state) => instance.Read());
  19:             readerCounter++;
  20:         }
  21:     }
  22:     ThreadPool.QueueUserWorkItem((state) => sw.Stop());
  23:     while (sw.IsRunning) ;
  24:  
  25:     return sw.ElapsedMilliseconds;
  26: }



Escenarios

Claramente la prueba es muy simple pues mide actividad sobre operaciones muy simples. Sin embargo, si miran el código de todas las clases en ellas esta comentado código que hace Sleep, esta inserción es la poco imaginatica implementacion de cierto tipo de costo en uso de recursos en una operación (la elección de Sleep es intencional pues dada la carga de requerimientos en el thread obliga a evaluar la ejecución de otro thread lo que también puede jugar un factor en escenarios de concurrencia). Con esta simple linea me permitió crear cuatro escenarios de ejemplo:

  • Operaciones sin costo: Tipicamente son aquellas operaciones donde el bloqueo tiene por unico sentido la sincronización y brindar propiedades thread-safe. En este caso, uno de los más comunes, la operación es muy corta y con muy poco impacto. Por ello para obtener un grado de medida adecuado esta operación tuvo que realizarse 100000 veces.
  • Operaciones con costo en acceso exclusivo: En este escenario la operación que requiere el bloqueo exclusivo (tipicamente la escritura) tiene un costo alto en tiempo (50 milisegundos), mientras que la operación con bloqueo compartido es bastante simple y rápida. Un escenario típico de este tipo de bloqueo son los caches implementados en consultas a servicios o fuentes de datos remotas donde la búsqueda inicial tiene un costo alto pero subsecuentes accesos son de bajo costo.
  • Operaciones con costo en acceso compartido: En este escenario es la operación de acceso compartido la que tiene un costo alto mientras que la operación con costo exclusivo es de corta duración. Este tipo de escenario es un poco más dificil de darse y la verdad solo lo hice como un ejercicio.
  • Operaciones con costo: Finalmente en este escenario ambas operaciones tiene un costo alto que es igual para ambas operaciones. Ejemplos de este tipo de escenarios se refieren principalmente al acceso sincronizado a recursos externos.

Nota: En ninguno de los casos analice el uso de recursos de sistema que puede ser un factor en el cual Monitor/lock salen faavorecidos por no usar objetos de sincronización de Kernel

Resultados

A continuación se presenta los resultados de las pruebas en un grafico que muestra el tiempo promedio de las operaciones ejecutadas durante la prueba

Operaciones sin costo: 10000 iteraciones sin costo,

Gráfico de linea de tiempo promedio

Como verán en este caso ReaderWriterLock es claramente un perdedor pero no en la proporción abismal que siempre leí.

Operaciones con costo en acceso exclusivo: 100 iteraciones con costo de 50 milisegundos

Gráfico de linea de tiempo promedio

En este caso la diferencia entre todos los mecanismos es muy baja y en distintas ejecuciones demostró ser prácticamente nulo

Operaciones con costo en acceso compartidos: 100 iteraciones con costo de 50 milisegundos

Gráfico de linea de tiempo promedio

En este escenario es muy claro que la ventaja la tienen los criterios de bloqueo compartido con una ventaja inicial a favor de la nueva clase del framework 3.5, Como dije anteriormente no se me ocurre un escenario común.

Operaciones con costo: 100 iteraciones con costo de 50 milisegundos

Gráfico de linea de tiempo promedio

Al igual que en el caso anterior Monitor/lock son claros perdedores conforme mayor proporción de lecturas vs. escrituras existen. Es más en este caso la ventaja es muy notoria aún con una baja proporción de distribución (2:1).

Volatile

Al terminar la prueba se me ocurrió que para el primero de los escenarios (propiedades thread-safe) tambíen pueden utilizarse variables volatiles así que hice una prueba y aqui estan los resultados de 100000 ejecuciones (dado que volatile no es una sección protegida solo el primer escenario es aplicable)

image

Como muestra el gráfico sacrificar el uso del cache de thread tiene un impacto en rendimiento bastante importante por lo que a pesar de su facilidad de uso y menor uso de recursos esta opción no es válida si lo que se busca es el mejor rendimiento.

Conclusiones

Considerando solo los tiempos y sin incluir el uso de recursos (que como mencioné anteriormente brinda una ventaja a Monitor/lock) las siguientes conclusiones vienen a mi cabeza:

  • Para sincronización en acceso a propiedades y cache claramente no hay ganancia en rendimiento apreciable en ninguno de los casos por lo que considerando el uso de recursos y facilidad de uso y lectura Monitor/lock/SyncLock parece ser la mejor opcion.
  • En aquellos casos donde la sincronización se realiza sobre una sección que puede tener un costo medio o alto en tiempo es recomendable el uso de ReadWriterLockSym en Framework 3.5 o superior, o ReadWriterLock en versiones anteriores. En particular el acceso sincronizado a recursos de red puede ser un muy escenario de uso