Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Nuevas características y un mejor rendimiento en Silverlight 4
Andrew Pardoe
Uno de los mayores cambios en Silverlight 4 fue el cambio a una versión nueva de CLR para el motor de ejecución del núcleo. Cada versión de .NET Framework ha usado el mismo CLR como su núcleo, desde Microsoft .NET Framework 2.0 hasta .NET Framework 3.5 SP1. .NET Framework 4 realizó algunos cambios, incluidos algunos muy grandes, como la exclusión del Perfil de cliente de fácil descarga y la disminución del tiempo de inicio al optimizar el diseño de archivos binarios nativos, pero siempre hemos estado sujetos a restricciones debido a la alta barra de compatibilidad impuesta debido a que es una actualización en contexto.
Con la versión .NET Framework 4, pudimos realizar cambios importantes en CLR a la vez que seguimos siendo altamente compatibles con las versiones anteriores. Silverlight 4 usa el nuevo CLR como la base para su CoreCLR y lleva todas sus mejoras desde el escritorio hasta la Web. Algunas de las mejoras en tiempo de ejecución más notables son un cambio en el comportamiento del recolector de elementos no usados (GC) predeterminado y el hecho de que ya no realizamos la compilación Just-in-Time (JIT) de los archivos binarios de Silverlight Framework cada vez que se ejecuta un programa de Silverlight. Hemos realizado mejoras en todas las clases bases, incluidas mejoras en el almacenamiento aislado y cambios en System.IO que permiten obtener acceso directo al sistema de archivos desde aplicaciones de Silverlight que se ejecutan con permisos elevados.
Comencemos con cierta información básica sobre cómo funciona el GC CoreCLR.
GC generacional
CoreCLR usa el mismo GC que el CLR de escritorio. Es un GC generacional, lo que significa que sus operaciones se basan en la heurística que establece que los objetos asignados más recientemente tienen más probabilidades de ser elementos no utilizados en el momento de la siguiente recolección. Esta heurística es evidente en los ámbitos pequeños: el programa no puede alcanzar los locales de función inmediatamente después de que se devuelve la función. Esta heurística generalmente se aplica también a ámbitos de mayor tamaño: los programas normalmente retienen un estado global en objetos que duran lo que dura la ejecución del programa.
Los objetos normalmente se asignan en la generación más reciente (a la que nos referiremos como Generación 0) y se promueve durante las recolecciones de elementos no utilizados (si sobreviven a la recolección) en generaciones anteriores hasta que alcanzan la generación máxima (Generación 2 en la implementación de GC de CLR actual).
Tenemos otra generación en el GC de CLR llamada Montón de objeto grande (LOH). Los objetos de gran tamaño (definidos actualmente como objetos de más de 85.000 bytes) se asignan directamente en el LOH. Este montón se recolecta al mismo tiempo que en la Generación 2.
Sin un GC generacional, el GC requiere inspeccionar el montón completo para saber a qué memoria se puede acceder y qué memoria es un elemento no utilizado antes de recolectar la memoria no utilizada. Con un GC generacional, no es necesario mirar el montón completo para cada recolección. Como la duración de una recolección está directamente relacionada con el tamaño de las generaciones que se recolectan, el GC está optimizado para recolectar sólo Generación 2 (y LOH) con menor frecuencia. Las recolecciones son casi inmediatas en montones pequeños y demoran más mientras más grandes son los montones. Las recolecciones de la Generación 0 pueden demorar hasta decenas de microsegundos.
Para la mayoría de los programas, la Generación 2 y LOH son mucho más grandes que la Generación 0 y la Generación 1, por lo que inspeccionar la memoria de estos montones demora más tiempo. Tenga en cuenta que el GC siempre recolecta la Generación 0 cuando recolecta la Generación 1, y que recolecta todos los montones cuando recolecta la Generación 2. Es por esta razón que la recolección es conocida como una recolección completa. Para obtener más detalles sobre el rendimiento de las distintas recolecciones de montones, consulte la columna de octubre “Todo sobre CLR” en el sitio web msdn.microsoft.com/magazine/ee309515.
GC simultáneo
El algoritmo sencillo para realizar una recolección de elementos no utilizados es hacer que el motor de ejecución ponga en pausa todos los subprocesos de programa mientras el GC realiza su trabajo. Nos referimos a este tipo de recolección como una recolección de bloqueo. Esta recolección permite que el GC traslade memoria no anclada (por ejemplo, para moverlo de una generación a la siguiente o para compactar segmentos de memoria pequeños) sin que el programa se dé cuenta de que algo ha cambiado. Si la memoria se moviese mientras se ejecutan subprocesos del programa, el programa pensaría que se ha dañado la memoria.
Pero hay parte del trabajo de una recolección de elementos no utilizados que no cambia la memoria. Desde la primera versión del CLR, hemos proporcionado un modo de GC que realiza recolecciones simultáneas. Éstas son recolecciones que realizan la mayor parte del trabajo de un GC completo sin tener que poner en pausa los subprocesos del programa por todo lo que dura esa recolección.
Hay diversas acciones que el GC puede realizar sin cambiar ningún estado que sea visible para el programa. Por ejemplo, el GC puede encontrar toda la memoria accesible por el programa. Los subprocesos del programa pueden continuar ejecutándose mientras que el GC inspecciona los montones. Antes de realizar la recolección real, el GC sólo necesita detectar qué es lo que ha cambiado mientras estuvo inspeccionando la memoria; por ejemplo, si el programa asignó un objeto nuevo, necesita estar marcado como accesible. Al finalizar esto, el GC solicita al motor de ejecución que bloquee todos los subprocesos, tal como lo haría en un GC no simultáneo, y continúa finalizando toda la memoria accesible en ese punto.
GC en segundo plano
El GC simultáneo siempre ha brindado una excelente experiencia en la mayoría de los escenarios, pero existe un escenario que hemos mejorado considerablemente. Recuerde que la memoria se asigna en la generación más reciente o en LOH. Las Generaciones 0 y 1 se ubican en un segmento único, al que llamamos “segmento efímero”, porque retiene objetos de duración breve. Cuando se completa el segmento efímero, el programa ya no puede crear objetos nuevos porque no hay espacio para ellos en el segmento efímero. El GC necesita realizar una recolección en el segmento efímero para liberar espacio y permitir que continúe la asignación.
El problema con el GC simultáneo es que no puede hacer nada de esto mientras se realiza una recolección simultánea. El subproceso del GC no puede trasladar memoria mientras se ejecutan subprocesos del programa (por lo que no puede promover objetos antiguos a la Generación 2), y como ya hay un GC en curso, no puede iniciar una recolección efímera. Pero el GC necesita liberar algo de memoria en el segmento efímero antes de que el programa pueda continuar. Es muy similar a un atasco: es necesario poner en pausa los subprocesos del programa, no porque el GC simultáneo esté cambiando un estado visible para el programa, sino porque el programa no puede realizar la asignación. Si una recolección simultánea encuentra que el segmento efímero está completo una vez que encuentra toda la memoria accesible, pondrá en pausa todos los subprocesos y realizará una compactación de bloqueo.
Este problema explica la motivación subyacente al desarrollo del GC en segundo plano. Funciona como el GC simultáneo en el sentido que el GC siempre realiza la mayor parte del trabajo de una recolección completa en su propio subproceso en segundo plano. La principal diferencia es que permite que se realice una recolección efímera mientras que la recolección completa recopila los datos. Esto significa que los programas pueden continuar ejecutándose cuando se completa el segmento efímero. El GC simplemente realiza una recolección efímera y todo se desarrolla según lo esperado.
El impacto que un GC en segundo plano tiene sobre la latencia del programa es considerable. Durante la ejecución del GC en segundo plano, observamos muchas menos pausas en la ejecución del programa y las que sí se realizaron fueron mucho más breves.
El GC en segundo plano es el modo predeterminado para Silverlight 4 y sólo está habilitado en plataformas Windows, porque el OS X carece de parte de la compatibilidad de SO que el GC necesita para ejecutarse en modo simultáneo o en segundo plano.
Mejoras de rendimiento de NGen
Los compiladores para lenguajes administrados como #C y Visual Basic no producen directamente código que se pueda ejecutar en la máquina del usuario. Estos compiladores producen un lenguaje intermedio llamado MSIL que se compila en código ejecutable durante la ejecución del programa a través del uso de un compilador JIT.
Usar MSIL representa muchos beneficios, que van desde la seguridad a la portabilidad, pero son necesarias dos compensaciones con el código compilado en modo JIT. Primero, mucho código .NET Framework se debe compilar antes de que se pueda compilar y ejecutar la función principal del programa. Esto significa que el usuario tiene que esperar el JIT antes de que el programa comience a ejecutarse. Segundo, cualquier código .NET Framework que se use se debe compilar para cada programa de Silverlight que se ejecuta en la máquina del usuario.
NGen ayuda con ambos problemas. NGen compila el código de .NET Framework en tiempo de instalación, por lo que ya se encuentra compilado cuando el programa comienza a ejecutarse. A menudo, el código que se ha compilado con NGen se puede compartir entre varios programas, por lo que el espacio de trabajo en la máquina del usuario se ve reducido cuando se ejecutan dos o más programas de Silverlight. Si desea obtener más información acerca de la manera en que NGen mejora el tiempo de inicio y el espacio de trabajo, consulte la columna de mayo de 2006 “Todo sobre CLR” msdn.microsoft.com/magazine/cc163610.
El código de .NET Framework constituye una gran parte de los programas de Silverlight, y el hecho de que NGen no esté disponible en Silverlight 2 y 3 marca una diferencia notable en tiempo de inicio. El compilador JIT demoraba demasiado tiempo en optimizar y compilar el código de biblioteca en la ruta de inicio de cada programa.
La solución que dimos a este problema fue no hacer que el compilador JIT optimizara la generación de código en Silverlight 2 y 3. El código todavía debía ser compilado, pero como el compilador JIT producía código simple, no demoraba mucho en realizar la compilación. En comparación con aplicaciones de escritorio tradicionales, la mayor parte de los programas escritos para escenarios web de aplicaciones de Internet enriquecidas son pequeños y no se ejecutan durante largo tiempo. Incluso más importante: normalmente son programas interactivos, lo que significa que pasan la mayor parte del tiempo esperando la entrada del usuario. En los escenarios a los que apuntaban Silverlight 2 y 3, tener un tiempo de inicio rápido era mucho más importante que la generación de código optimizado.
A medida que las aplicaciones web de Silverlight han evolucionado, hemos realizado cambios para que la experiencia siga siendo positiva. Por ejemplo, agregamos compatibilidad para la instalación y la ejecución de aplicaciones Silverlight desde el escritorio en Silverlight 3. Normalmente, estas aplicaciones tienen mayor tamaño y realizan más trabajo que las aplicaciones pequeñas e interactivas que se encuentran en el escenario web clásico. Silverlight agregó gran cantidad de funcionalidad que requiere muchos cálculos, como la compatibilidad con entrada táctil en Windows 7 y manipulación enriquecida de fotografías, tal como puede ver en el sitio web de Bing Maps. Todos estos escenarios requieren que el código se optimice para realizar un trabajo eficaz.
Silverlight 4 le brinda código optimizado y rendimiento de inicio. El compilador JIT ahora usa las mismas optimizaciones en Silverlight que usa para las aplicaciones .NET de escritorio. Tenemos la capacidad de realizar las optimizaciones, porque hemos habilitado NGen para ensamblados .NET Framework de Silverlight. Cuando instala Silverlight, compilamos automáticamente todo el código administrado que se incluye en tiempo de ejecución de Silverlight y lo guardamos en el disco duro. Cuando un usuario ejecuta el programa Silverlight, se comienza a ejecutar sin tener que esperar que se compile otro código de Framework. Igual de importante es que ahora optimizamos el código en el programa Silverlight, para que los programas se ejecuten más rápidamente, y podemos compartir el código Framework entre los diversos programas Silverlight que se ejecutan en la máquina del usuario.
Silverlight 4 crea imágenes nativas de los ensamblados de .NET Framework durante la instalación. Hay algunas aplicaciones en las que el rendimiento de inicio es el único rendimiento que importa. Piense por ejemplo en el Bloc de notas: es importante que se inicie rápidamente, pero una vez que comienza a escribir no importa la rapidez con que el Bloc de notas se ejecute (siempre que su ejecución sea más rápida que su escritura). Para esta clase de programas, el tiempo que JIT demora en compilar el código de inicio de la aplicación puede provocar una disminución en el rendimiento. La mayoría de las aplicaciones se iniciarán 400 ms a 700 ms más rápido en Silverlight 4 y se verá una mejora en el rendimiento del 60% durante la ejecución.
La Biblioteca de clases base (BCL) es el centro de las API administradas que ahora son compatibles con NGen en Silverlight 4. Echemos un vistazo a las novedades de la BCL.
Nueva funcionalidad de BCL
Muchas de las nuevas mejoras de BCL en Silverlight 4 también son nuevas en .NET Framework 4 y ya se describieron en ese contexto. Daré una breve información general sobre lo que incluimos en Silverlight 4.
Los contratos de código brindan una manera integrada de expresar condiciones previas, condiciones posteriores y condiciones invariables en el código Silverlight. Los contratos de código se pueden usar para expresar de mejor manera los supuestos del código y pueden ayudar a encontrar tempranamente errores. El uso de contratos de código presenta muchos beneficios adicionales. Puede obtener más información en la columna “Todo sobre CLR” de Melitta Andersen de agosto de 2009 en msdn.microsoft.com/magazine/ee236408 o en el sitio DevLabs de contratos de código enmsdn.microsoft.com/devlabs/dd491992 o en el blog del equipo de BCL en el sitio web blogs.msdn.com/bclteam.
Las tuplas se usan más a menudo para devolver varios valores desde un método. A menudo se usan en lenguajes funcionales como F#, y en lenguajes dinámicos como IronPython, pero son tan fáciles de usar como en Visual Basic y C#. Puede obtener más información acerca del diseño de tuplas en la columna "Todo sobre CLR" de Matt Ellis de julio de 2009 en msdn.microsoft.com/magazine/dd942829.
Lazy<T> proporciona una manera simple de inicializar objetos de manera diferida. La inicialización diferida es una técnica que pueden usar las aplicaciones para diferir la carga o la inicialización de datos hasta que se necesiten por primera vez.
Los nuevos tipos de datos numéricos BigInteger y Complex están disponibles en el SDK de Silverlight 4 en System.Numerics.dll. BigInteger representa un entero con precisión arbitraria y Complex representa un número complejo con componentes reales e imaginarios.
Enum, Guid y Version ahora son compatibles con TryParse como muchos de los demás tipos de datos de BCL, lo que brinda una manera más eficaz de crear una instancia desde una cadena que no arroja excepciones sobre los errores.
Enum.HasFlag es un nuevo método de conveniencia que se puede usar para comprobar fácilmente si se establece o no una marca en una enumeración de marcas, sin tener que recordar cómo usar los operadores bit a bit.
String.IsNullOrWhiteSpace es un método de conveniencia que comprueba si una cadena está o no nula, vacía o si sólo contiene un espacio en blanco.
Las sobrecargas Join y String.Concat ahora toman un parámetro IEnumerable<T>. Estas nuevas sobrecargas para String.Concat y Join permiten concatenar cualquier recolección que implementa IEnumerable<T> sin la necesidad de convertir primero la recolección en una matriz.
Stream.CopyTo facilita leer desde una secuencia y escribir los contenidos en otra secuencia en una línea de código.
Además de estas características nuevas, también hemos realizado algunas mejoras en el almacenamiento aislado y habilitados las aplicaciones Silverlight de confianza para obtener acceso directamente a partes del sistema de archivos a través de System.IO.
Mejoras en el almacenamiento aislado
El almacenamiento aislado es un sistema de archivos virtual que pueden usar las aplicaciones Silverlight para almacenar datos en el cliente. Para obtener más información acerca del almacenamiento aislado en Silverlight, consulte la columna “Todo sobre CLR” de marzo de 2009 en msdn.microsoft.com/magazine/dd458794.
La mejora más notable en el almacenamiento aislado de Silverlight 4 está en el área del rendimiento. Desde el lanzamiento de Silverlight 2, hemos recibido muchos comentarios de los desarrolladores respecto del rendimiento del almacenamiento aislado. En Silverlight 3, hicimos algunos cambios que mejoraron considerablemente el rendimiento de la lectura de datos desde el almacenamiento aislado. En Silverlight 4, hemos ido un paso más allá y abordamos los cuellos de botella en el rendimiento que los desarrolladores veían cuando escribían datos en el almacenamiento aislado. En términos generales, el rendimiento del almacenamiento aislado se ha mejorado mucho en Silverlight 4.
También escuchamos de parte de los desarrolladores que no había una manera simple de cambiar el nombre de los archivos o copiarlos dentro del almacenamiento aislado. A fin de cambiar el nombre de un archivo, era necesario leer manualmente desde el archivo original, crear y escribir un archivo nuevo y luego eliminar el archivo original. Cambiar el nombre de un directorio podría hacerse de manera similar, pero requiere incluso más líneas de código, en especial cuando el directorio al que desea cambiarle el nombre contiene subdirectorios. Esto funciona, pero es más código del que debiera escribir y no es tan eficiente como indicarle al SO que simplemente cambie el nombre del archivo o del directorio en el disco.
En Silverlight 4, agregamos métodos nuevos a la clase IsolatedStorageFile que puede llamar para realizar estas operaciones de manera eficaz en una sola línea de código: CopyFile, MoveFile y MoveDirectory. También agregamos métodos nuevos que brindan información adicional acerca de los archivos y los directorios dentro del almacenamiento aislado: GetCreationTime, GetLastAccessTime y GetLastWriteTime.
Otra API nueva que agregamos en Silverlight 4 es IsolatedStorageFile.IsEnabled. Anteriormente, la única manera para determinar si el almacenamiento aislado estaba habilitado era intentar usarlo y luego capturar la IsolatedStorageException subsiguiente, que se arroja si el almacenamiento aislado está deshabilitado. La nueva propiedad estática IsEnabled se puede usar para determinar con mayor facilidad si un almacenamiento aislado está o no habilitado.
Muchos exploradores, como Internet Explorer, Firefox, Chrome y Safari, ahora son compatibles con un modo de exploración privado en el que el historial de exploración, las cookies y otros datos no se conservan. Silverlight 4 respeta la configuración de exploración privada, con lo que se evita que las aplicaciones obtengan acceso al almacenamiento aislado y almacenen información en su máquina local cuando el explorador se encuentra en modo privado. En tales circunstancias, la propiedad IsEnabled se devolverá False y cualquier intento de usar almacenamiento aislado generará una IsolatedStorageException, el mismo comportamiento que ocurriría si el usuario hubiese deshabilitado explícitamente el almacenamiento aislado.
Acceso al sistema de archivos
Las aplicaciones Silverlight se ejecutan en un espacio aislado de confianza parcial. El espacio aislado de seguridad restringe el acceso a la máquina local y coloca varias restricciones sobre la aplicación, lo que evita que código malintencionado provoque daño. Por ejemplo, las aplicaciones Silverlight de confianza parcial no pueden obtener acceso directamente al sistema de archivos. Si una aplicación necesita almacenar datos en el cliente, su única opción es almacenar los datos dentro del almacenamiento aislado. Sólo es posible obtener acceso al sistema de archivos más amplio a través de OpenFileDialog o SaveFileDialog.
Silverlight 3 agregó la capacidad de instalar y ejecutar aplicaciones fuera del explorador. Esto permite que existan algunos interesantes escenarios sin conexión, pero dichas aplicaciones siguen ejecutándose dentro del mismo espacio aislado que las aplicaciones que se ejecutan dentro del explorador. Silverlight 4 permite que las aplicaciones fuera del explorador se configuren para ejecutarse con confianza elevada. Dichas aplicaciones de confianza pueden omitir algunas de las restricciones del espacio aislado después de la instalación. Por ejemplo, las aplicaciones de confianza pueden obtener acceso a los archivos del usuario, usar redes sin restricciones de acceso entre dominios, omitir los requisitos de iniciación y consentimiento del usuario y obtener acceso a la funcionalidad del SO nativo.
Cuando un usuario instala una aplicación que requiere confianza elevada, el aviso de instalación normal se reemplaza con una advertencia que indica al usuario que la aplicación puede obtener acceso a los datos de usuario y que sólo se debe instalar desde sitios web de confianza.
Las aplicaciones de confianza pueden usar las API en System.IO para obtener acceso directamente a los siguientes directorios de usuario en el sistema de archivos: Mis documentos, Mi música, Mis imágenes y Mis vídeos. Actualmente, las operaciones de archivo fuera de estos directorios no se permiten y generarán una SecurityException. Dentro de estos directorios, se permiten todas las operaciones de archivo, incluida lectura y escritura. Por ejemplo, una aplicación de confianza de álbum fotográfico puede obtener acceso directamente a todos los archivos dentro del directorio Mis imágenes. Una aplicación de confianza para la edición de vídeos puede guardar una película en el directorio Mis vídeos.
Es importante no codificar de forma rígida las rutas de acceso al sistema de archivos a estos directorios en sus aplicaciones, puesto que las rutas de acceso serán diferentes según el SO subyacente. Las rutas de acceso al sistema de archivos son completamente diferentes entre Windows y Mac OS X, pero también pueden ser distintas entre diversas versiones de Windows. Para que funcionen correctamente en todas las plataformas, se debe usar System.Environment.GetFolderPath para obtener las rutas de acceso al sistema de archivos para estos directorios. El siguiente código usa Environment.GetFolderPath para obtener la ruta de acceso al sistema de archivos al directorio Mis imágenes, encuentra todos los archivos dentro de Mis imágenes (y subdirectorios) que finalicen con .jpg mediante el uso del método System.Directory.EnumerateFiles y agrega cada ruta de archivo a un ListBox:
if (Application.Current.HasElevatedPermissions) {
string myPictures = Environment.GetFolderPath(
Environment.SpecialFolder.MyPictures);
IEnumerable<string> files =
Directory.EnumerateFiles(myPictures, "*.jpg",
SearchOption.AllDirectories);
foreach (string file in files) {
listBox1.Items.Add(file);
}
}
Este código muestra cómo crear un archivo de texto en el directorio Mis documentos del usuario desde una aplicación de confianza:
if (Application.Current.HasElevatedPermissions) {
string myDocuments = Environment.GetFolderPath(
Environment.SpecialFolder.MyDocuments);
string filename = "hello.txt";
string file = Path.Combine(myDocuments, filename);
try {
File.WriteAllText(file, "Hello World!");
}
catch {
MessageBox.Show("An error occurred.");
}
}
System.IO.Path.Combine se usa para combinar la ruta a Mis documentos con el nombre del archivo, lo que insertará el carácter separador para el directorio adecuado para la plataforma subyacente entre los dos (Windows usa \, mientras que Mac usa /). File.WriteAllText se usa para crear el archivo (o para sobrescribirlo si ya existe) y escribir el texto “Hello World!” en el archivo.
Mejor rendimiento y más capacidades
Como puede ver, el nuevo CLR en Silverlight 4 incluye mejoras tanto en tiempo de ejecución como en las clases base. El nuevo comportamiento de GC, el hecho de que ahora usamos NGen con los ensamblados de Silverlight Framework y las mejoras en el rendimiento del almacenamiento aislado significan que las aplicaciones se iniciarán más rápidamente y se ejecutarán mejor en Silverlight 4. Las mejoras en la BCL permiten que las aplicaciones realicen más trabajo con menos código, y las nuevas capacidades, como la capacidad de las aplicaciones de confianza para obtener acceso al sistema de archivos, facilitan la creación de nuevos escenarios de aplicaciones atractivos.
Andrew Pardoe es jefe de programas para CLR de Microsoft. Trabaja en muchos aspectos del motor de ejecución tanto para tiempos de ejecución de escritorio como Silverlight. Puede ponerse en contacto con él en andrew.pardoe@microsoft.com.
Justin Van Patten es jefe de programas en el equipo de CLR de Microsoft, donde trabaja en las Bibliotecas de clases base. Puede ponerse en contacto con él a través del blog del equipo de BCL en https://blogs.msdn.com/b/bclteam/.
Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Surupa Biswas, Vance Morrison y Maoni Stephens