Modelo de ejecucion del .NET Framework. El CLR

by Valeriano Tortola 28. agosto 2007 04:48

Siguiendo con el artículo anterior donde explicaba que es el código administrado MSIL, voy a explicar ahora también de forma breve como el CLR (Common Language Runtime) ejecuta dicho código con la ayuda del JIT (Just In Time compiler), y cuales son las ventajas y características de este modelo de ejecución. Este es más extenso que el anterior, aunque está todo bastante resumido y explicado por encima espero que resulte interesante. 

Como comentaba, una vez generado el código administrado se compone un ensamblado junto los metadatos, el manifiesto que lo describe y los recursos que se hayan decidido incluir en él, estos datos permiten al CLR obtener información del ensamblado, sus tipos y recuperar el código administrado cuando se requiera.

El CLR es una máquina virtual, un entorno de ejecución para nuestro código MSIL, que provee las siguientes características:

  • Provee de un modelo consistente y homogéneo:
    • Basado plenamente en POO.
    • Todo son ensamblados, aunque se pueden usar elementos convencionales como .dll nativas, objetos COM+ y similares, siempre se realiza de una forma convenida.
    • Los errores siempre se disparan en forma de Excepción.
    • Es un modelo fuertemente tipado y el CLR verifica el código antes de su ejecución.
  • Permite trabajar con elementos no admnistrados como librerias en código nativo ó objetos COM+ mediante interoperabilidad.
  • Ejecuta todas las aplicaciones administradas dentro de un mismo espacio de direccionamiento virtual del sistema operativo de forma que se gana rendimiento al no tener que estar solicitando recursos al sistema operativo constantemente, pero cada aplicación se ejecuta en un dominio de aplicación del framework, que es un contenedor que impide que unas aplicaciones accedan a la memoria de otras, teniendo de esta forma la misma robustez y seguridad que si se ejecutasen en sus propios espacios de direccionamiento virtual.
  • Elimina el problema del "dll Hell", ya que los ensamblados van firmados con su número de versión en el manifiesto, lo que permite que numerosas dll con el mismo nombre y hasta la misma funcionalidad coexistan con diferentes números de versión.
  • Permite soporte multiplataforma, ya que el código administrado es independiente de la plataforma, un CLR para Linux podría ejecutar perfectamente dicho código.
  • Provee de un sistema de gestión de memoria encargado de recolectar la memoria en desuso u ocupada por objetos desreferenciados, el recolector de basura (Garbage Collector).
  • Soporte multithreading, que le permite ejecutar aplicaciones que trabajan con varios hilos de ejecución en paralelo.
  • Diversas técnicas para mejorar el rendimiento como la cache de objetos que permite la conservación y posterior reutilización de objetos que son costosos de crear, evadiendo la recolección de basura mientras permanecen en la cache. Un ejemplo de esto es el ThreadPool
  • Seguridad CAS (Code Access Security), un sistema de permisos independiente al del sistema operativo y la plataforma con el que el CLR puede evaluar si el código requerido tiene permisos para ejecutarse.
  • Seguridad basada en roles (Roled Based Security), un sistema de seguridad basado en perfiles de usuario también independiente del sistema operativo aunque se puede integrar con él.

Cuando ejecutamos un ensamblado ejecutable (una aplicación, no una libreria),  el CLR procede de la siguiente forma:

  1. Busca la entrada, normalmente la clase Program.
  2. Inicializa la clase inicializando todos sus campos.
  3. Busca el punto de entrada, normalmente el método Main.
  4. Detecta todos los tipos que son referenciados desde él y reserva la memoria oportuna.
  5. Empieza la ejecución, encuentra el primer método y lo busca en el ensamblado utilizando los metadatos.
  6. Procede con este método como en el punto 4.
  7. Carga el código administrado MSIL.
  8. Verifica el código MSIL asegurando que es seguro. Verifica cosas como :
    • Que cada método es invocado con el número y tipo correcto de parámetros.
    • Que los valores de retorno se reciben apropiadamente.
    • Que cada método tiene una instrucción de retorno.
  9. Llama a una función interna denominada JITCompiler (Just In Time Compiler), conocido también como JIT ó JITer. Esta función es la encargada de compilar el código administrado a código nativo, y se guarda en memoria dinámica.
  10. Se ejecuta el código nativo. Si se vuelve a ejecutar el mismo método, directamente el JITCompiler devuelve la dirección de memoria donde se compiló anteriormente, de forma que no se necesita verificarlo ni compilarlo dos veces.
  11. Continua la ejecución con el siguiente método de la misma forma.

El proceso de compilación dinámica produce una perdida de rendimiento y un mayor uso de memoria dinámica, pero es mínimo, además este proceso aporta una serie de ventajas bastante interesantes, echemos un vistazo más profundo al funcionamiento del JIT:

  • Compila el código administrado a código nativo y lo almacena en memoria dinámica, si se requiere usar de nuevo, se obtiene directamente el código nativo sin tener que volver a compilarlo. Cuando la aplicación termina dicho código se desecha.
  • Ya que se almacena en memoria dinámica, si se ejecuta el ensamblado en otro proceso Windows distinto, no se puede aprovechar la compilación anterior y se vuelve a compilar de nuevo.
  • Optimiza el código nativo al igual que un compilador C++, con la penalización de rendimiento que esto conlleva, pero la ejecución se realizará con un rendimiento mucho mayor que compensa de sobra la penalización anterior.
  • Puede determinar que CPU explícita (Pentium IV, Pentium III,...etc..) tiene como objetivo en tiempo de ejecución y generar código nativo optimizado para dicho objetivo, mientras que las compilaciónes de lenguajes convencionales suelen compilar para familias genéricas. De esta forma, el código nativo generado está siempre optimizado al máximo.
  • Puede optimizar el uso de variables que se encuentran en bucles mediante el uso de caches. Aunque en entornos multithreading esto puede representar un serio problema.
  • Puede determinar cuando la evaluación de determinadas condiciones son siempre True ó False en la máquina donde se ejecutan (como por ejemplo, evaluar cuantos procesadores hay), obviando la generación de código nativo para estas instruciones y asignando un valor directo, con lo que se gana velocidad de ejecución.
  • El CLR puede monitorizar y perfilar la ejecución del código MSIL, reorganizar y recompilar para mejorar el rendimiento en base a unos patrones de observación.

Gracias a todas estas características el decir que una aplicación en código administrado es más lenta que la misma en código nativo puede ser una afirmación precipitada, de hecho, puede ser hasta más rápida. De la misma forma, usar nGen no garantiza una ejecución más rápida, aunque si suele mejorar el tiempo de puesta en marcha. Otro día hablaré de nGen :D

Tags: , , ,

.NET 2.0

Comentarios

28/08/2007 16:57:30 #

trackback

Trackback from vtortola

Modelo de ejecucion del .NET Framework. El CLR

vtortola |

24/10/2007 5:00:31 #

pingback

Pingback from geeks.ms

La memoria en .NET, tipos de variables - Pensando en asíncrono

geeks.ms |

24/10/2007 5:04:03 #

pingback

Pingback from elbruno.com

La memoria en .NET, tipos de variables - vtortola

elbruno.com |

Comentarios no permitidos