0

.NET 6 Release Candidate 1

Ya tenemos entre nosotros la primer lados release candidate planeadas para .net que fue lanzada este 14 de septiembre. En estos ultimos meses después de la preview 7, el equipo de desarrollado se ha centrado exclusivamente en mejoras de calidad que resuelven problemas funcionales o de rendimiento en nuevas características o regresiones en las existentes.‎

‎Puede ‎‎descargar .NET 6 Release Candidate 1‎‎ para Linux, macOS y Windows.‎

‎.NET 6 RC1 se ha probado y es compatible con ‎‎Visual Studio 2022 Preview 4‎‎. Visual Studio 2022 nos permite aprovechar las herramientas de Visual Studio desarrolladas para .NET 6, como el desarrollo en .NET MAUI, Hot Reload para aplicaciones de C#, la nueva versión preliminar de Web Live para WebForms y otras mejoras de rendimiento en su experiencia de IDE.‎

‎La compatibilidad con .NET 6 RC1 llegará pronto en Visual Studio 2022 para Mac Preview 1, que actualmente está disponible como vista previa privada.‎

Compilación de origen‎

‎La compilación‎‎ de origen es un escenario y también una infraestructura en la que se ha estado trabajando en colaboración con Red Hat desde antes del envío de .NET Core 1.0. Varios años después, se está muy cerca de entregar una versión totalmente automatizada de la misma. Para los usuarios de Red Hat Enterprise Linux (RHEL) .NET, esta ‎‎capacidad es un gran problema‎‎. Red Hat dice que .NET ha crecido hasta convertirse en una importante plataforma de desarrollo para su ecosistema. 

‎Claramente, el código fuente de .NET se puede integrar en binarios. Los desarrolladores lo hacen todos los días después de clonar un repositorio de la ‎‎organización dotnet‎‎. Eso no es realmente de lo que se trata.‎

‎El ‎‎estándar de oro‎‎ para las distribuciones de Linux es construir código fuente abierto utilizando compiladores y cadenas de herramientas que forman parte del archivo de distribución. Eso funciona para el tiempo de ejecución de .NET (escrito en C ++), pero no para ninguno de los códigos escritos en C #. Para el código de C#, usamos un mecanismo de compilación de dos pasadas para satisfacer los requisitos de distribución. Es un poco complicado, pero es importante entender el flujo.‎

‎Red Hat crea el origen del SDK de .NET utilizando la compilación de Microsoft del SDK de .NET (#1) para producir una compilación de código abierto puro del SDK (#2). Después de eso, el mismo código fuente del SDK se construye nuevamente usando esta nueva compilación del SDK (# 2) para producir un SDK de código abierto demostrable (# 3). Este SDK final (#3) se pone a disposición de los usuarios de RHEL. Después de eso, Red Hat puede usar este mismo SDK (# 3) para crear nuevas versiones de .NET y ya no necesita usar el SDK de Microsoft para crear actualizaciones mensuales.‎

‎Ese proceso puede ser sorprendente y confuso. Las distribuciones de código abierto deben ser construidas por herramientas de código abierto. Este patrón garantiza que la compilación de Microsoft del SDK no sea necesaria, ya sea por intención o por accidente. Hay una barra más alta, como plataforma de desarrollo, para ser incluido en una distribución que solo usa una licencia compatible. El proyecto de compilación de origen ha permitido que .NET cumpla con esa barra.‎

‎La entrega para la compilación de origen es un tarball de origen. El tarball de origen contiene todo el origen del SDK (para una versión determinada). A partir de ahí, Red Hat (u otra organización) puede crear su propia versión del SDK. La política de Red Hat requiere el uso de una cadena de herramientas construida a partir de la fuente para producir una bola de alquitrán binaria, por lo que utilizan una metodología de dos pasadas. Pero este método de dos pasadas no es necesario para la compilación de origen en sí.‎

‎Es común en el ecosistema Linux tener paquetes o tarballs tanto de origen como binarios disponibles para un componente determinado. Ya teníamos tarballs binarios disponibles y ahora también tenemos tarballs de origen. Eso hace que .NET coincida con el patrón de componentes estándar.‎

‎La gran mejora en .NET 6 es que el tarball de origen es ahora un producto de nuestra compilación. Solía requerir un esfuerzo manual significativo para producir, lo que también resultó en una latencia significativa al entregar el tarball de origen a Red Hat. Ninguna de las partes estaba contenta con eso.‎

Se ha estado trabajando estrechamente con Red Hat en este proyecto durante más de cinco años. Ha tenido éxito, en gran parte, gracias a los esfuerzos de los excelentes ingenieros de Red Hat. Otras distribuciones y organizaciones se beneficiarán de sus esfuerzos.‎

‎Optimización guiada por perfiles (PGO)‎

‎La optimización guiada por perfiles (PGO)‎‎ es una capacidad importante de la mayoría de las plataformas de desarrolladores. Se basa en la suposición de que el código ejecutado como parte del inicio es a menudo uniforme y que el rendimiento de nivel superior se puede ofrecer explotando eso.‎

‎Hay muchas cosas que puede hacer con PGO, tales como:‎

  • ‎Compilar código de inicio con mayor calidad.‎
  • ‎Reducir el tamaño binario compilando código de bajo uso con menor calidad (o no lo haga en absoluto).‎
  • ‎Reorganizar los archivos binarios de la aplicación de modo que el código utilizado en el inicio se ubique cerca del inicio del archivo.‎

‎.NET ha utilizado PGO en varias formas durante veinte años. El sistema que se desarrolló inicialmente era propietario y muy difícil de usar. Era tan difícil de usar que muy pocos otros equipos de Microsoft lo usaron a pesar de que podría haber proporcionado un beneficio significativo. Con .NET 6, se decidió reconstruir el sistema PGO desde cero. Esto fue motivado en gran parte por ‎‎crossgen2‎‎ como la nueva tecnología habilitadora.‎

‎Hay varios aspectos para habilitar un sistema PGO de clase mundial (al menos en nuestra opinión):‎

  • ‎Herramientas de capacitación fáciles de usar que recopilan datos de PGO de las aplicaciones, en el escritorio del desarrollador y / o en producción.‎
  • ‎Integración sencilla de datos PGO en el flujo de compilación de aplicaciones y bibliotecas.‎
  • ‎Herramientas que procesan datos PGO de varias maneras (diferenciación y transformación).‎
  • ‎Formato de texto amigable para humanos y control de código fuente para datos PGO.‎
  • ‎Los datos estáticos de PGO pueden ser utilizados por un sistema PGO dinámico para establecer una visión inicial.‎

‎En .NET 6, nos centramos en crear la base que pueda permitir esas y otras experiencias. En este lanzamiento, acabamos de volver a lo que teníamos antes. Las bibliotecas de tiempo de ejecución se compilan ‎‎en formato listo para ejecutar‎‎ optimizado con (la nueva forma de) datos PGO. Todo esto está habilitado con crossgen2. En la actualidad, no hemos permitido que nadie más use PGO para optimizar aplicaciones. Eso es lo que vendrá a continuación con .NET 7.‎

‎PGO dinámico‎

‎El PGO dinámico‎‎ es la imagen especular del sistema PGO estático que nuevo  . Donde el PGO estático se integra con crossgen2, el PGO dinámico se integra con RyuJIT. Cuando el PGO estático requiere una actividad de entrenamiento separada y el uso de herramientas especiales, el PGO dinámico es automático y utiliza la aplicación en ejecución para recopilar datos relevantes. Cuando se conservan los datos estáticos de PGO, los datos dinámicos de PGO se pierden después de cada ejecución de la aplicación. El PGO dinámico es similar a un ‎‎JIT de rastreo‎‎.‎

‎Dynamic PGO es actualmente opt-in, con las siguientes variables de entorno establecidas.‎

  • DOTNET_TieredPGO=1
  • DOTNET_TC_QuickJitForLoops=1

‎La ‎‎publicación Mejoras de rendimiento en .NET 6‎‎ hace un gran trabajo al demostrar cómo el PGO dinámico mejora el rendimiento.‎

‎La compilación por niveles (CT)‎‎ tiene características similares a las del PGO dinámico. De hecho, el PGO dinámico puede considerarse como una compilación escalonada v2. La CT proporciona muchos beneficios, pero no es sofisticada en múltiples dimensiones y se puede mejorar en gran medida. Es cerebro para un espantapájaros.‎

‎Quizás la capacidad más interesante de PGO dinámico es la desvirtualización. El costo de las llamadas al método se puede describir así: interfaz > virtual sin interfaz > no virtual. Si podemos transformar una llamada al método de interfaz en una llamada no virtual, entonces eso es una mejora significativa del rendimiento. Eso es súper difícil en el caso general, ya que es muy difícil saber estáticamente qué clases implementan una interfaz determinada. Si se hace mal, el programa (con suerte) se bloqueará. Dynamic PGO puede hacer esto de manera correcta y eficiente.‎

‎RyuJIT ahora puede generar código utilizando el patrón de compilador de «desvirtualización protegida». Dynamic PGO recopila datos sobre las clases reales que satisfacen una interfaz en alguna parte de una firma de método en tiempo de ejecución. Si hay un fuerte sesgo en una clase, puede decirle a RyuJIT que genere código que prefiera esa clase y use llamadas directas al método en términos de esa clase específica. Como se sugiere, las llamadas directas son mucho más rápidas. Si, en el caso inesperado, el objeto es de una clase diferente, la ejecución saltará a un código más lento que utiliza el despacho de la interfaz. Este patrón preserva la corrección, no es mucho más lento en el caso inesperado y es mucho más rápido en el caso típico esperado. Este sistema de modo dual se llama guardado ya que el código desvirtualizado más rápido solo se ejecuta después de una verificación de tipo exitosa.‎

Crossgen2

Crossgen2 es un gran paso adelante para la compilación anticipada o previa para la plataforma. Hay varios aspectos en esto que sientan las bases para futuras inversiones y capacidades. No es obvio, pero crossgen2 puede ser la característica fundamental más prometedora de la versión. 

El aspecto más importante es que crossgen2 tiene el objetivo de diseño de ser un compilador independiente. Crossgen1 era una compilación separada del tiempo de ejecución con solo los componentes necesarios para permitir la generación de código. Ese enfoque fue problemático por varias razones diferentes. Hizo el trabajo, pero eso es todo.‎

‎Como resultado de ser un compilador independiente, podría escribirse en cualquier idioma. Naturalmente, elegimos C#, pero también podría haber sido escrito en Rust o JavaScript. Solo necesita poder cargar una compilación determinada de RyuJIT como un complemento y comunicarse con él con el protocolo prescrito.‎

‎Del mismo modo, la naturaleza independiente le permite ser de orientación cruzada. Puede, por ejemplo, apuntar a ARM64 desde x64, Linux desde Windows o .NET 6 desde .NET 7.‎

‎Cubriré algunos de los escenarios que estamos inmediatamente interesados en habilitar, después de .NET 6. De aquí en adelante, solo usaré «crossgen», pero me refiero a «crossgen2».‎

‎De forma predeterminada, ‎‎el código listo para ejecutar (R2R)‎‎ tiene la misma capacidad de versión que IL. Ese es objetivamente el valor predeterminado correcto. Si no está seguro de cuáles son las implicaciones de eso, eso demuestra que elegimos el valor predeterminado correcto. En .NET 6, agregamos un nuevo modo que extiende el límite de versión de un único ensamblado a un grupo de ensamblados. A eso lo llamamos una «burbuja de versión». Hay dos capacidades principales que permiten las burbujas de versión: la alineación de métodos y las instanciaciones genéricas de ensamblaje cruzado (como si estuvieran en diferentes ensamblajes). El primero nos permite generar código de mayor calidad y el segundo permite generar código R2R donde de lo contrario tenemos que confiar en el JIT. Esta función ofrece beneficios de inicio de dos dígitos en nuestras pruebas. El único inconveniente es que las burbujas de versión suelen generar más código como resultado. Ahí es donde la próxima capacidad puede ayudar.‎List<string>List<T>string

‎Hoy en día, crossgen genera código R2R para todos los métodos de un ensamblado, incluidos el tiempo de ejecución y el SDK. Eso es muy derrochador, ya que probablemente al menos la mitad de ellos sería mejor dejarlos para el jitting en tiempo de ejecución (si es que son necesarios). Crossgen ha tenido la capacidad de compilación parcial durante mucho tiempo, e incluso lo hemos utilizado. En .NET Core 3.0, lo usamos para quitar unos 10 MB de la distribución en tiempo de ejecución en Linux. Lamentablemente, esa configuración se perdió en algún momento y ahora estamos llevando esos 10 MB adicionales. Para .NET 7, vamos a tomar otra grieta en esto, y esperamos identificar mucho más de 10 MB de código R2R para ya no generar (lo que naturalmente tiene beneficios más allá de la reducción de tamaño).‎

‎Las instrucciones vectoriales o SIMD‎‎ se explotan significativamente en las bibliotecas .NET y son fundamentales para ofrecer un alto rendimiento. De forma predeterminada, crossgen utiliza una versión antigua (SSE2) de estas instrucciones y se basa en la compilación por niveles para generar las mejores instrucciones SIMD para una máquina determinada. Eso funciona, pero no es óptimo para el hardware moderno (como en la nube) y es particularmente problemático para aplicaciones sin servidor de ejecución corta. Crossgen permite especificar un conjunto de instrucciones SIMD moderno y mejor como AVX2 (para Intel y AMD). Planeamos cambiar a la producción de imágenes listas para ejecutar para AVX2 con esta nueva capacidad para .NET 7. Esta capacidad no es actualmente relevante para el hardware Arm64, ya que NEON es universal y las mejores instrucciones disponibles. Cuando SVE y SVE2 se conviertan en un lugar común, tendremos que implementar un modelo similar para Arm64.‎

‎Cualquiera que sea la configuración de crossgen más óptima, así es como entregaremos imágenes de contenedores. Vemos los contenedores como nuestro tipo de distribución más libre de legado y queremos explotarlo mejor. Vemos muchas oportunidades para «totalmente optimizado por defecto» para contenedores.‎

‎Mitigaciones de seguridad‎

Se agrego soporte de vista previa para dos mitigaciones de seguridad clave esta versión: ‎‎CET‎‎ y W^X. Tenemos la intención de habilitarlos de forma predeterminada en .NET 7.‎

CET

‎La tecnología de aplicación de flujo de control (CET) de‎‎ Intel es una característica de seguridad disponible en algunos procesadores Intel y AMD más nuevos. Agrega capacidades al hardware que protegen contra alguno+s tipos comunes de ataques que involucran secuestro de flujo de control. Con las pilas de sombras CET, el procesador y el sistema operativo pueden realizar un seguimiento del flujo de control de llamadas y devoluciones en un subproceso en la pila de sombras, además de la pila de datos, y detectar cambios no deseados en el flujo de control. La pila de sombras está protegida de los accesos a la memoria del código de la aplicación y ayuda a defenderse contra ataques que involucran programación orientada al retorno (ROP).‎

W^X

‎W^X‎‎ es una de las mitigaciones más fundamentales. Bloquea la ruta de ataque más simple al no permitir que las páginas de memoria sean escriturables y ejecutables al mismo tiempo. La falta de esta mitigación ha llevado a que no consideremos mitigaciones más avanzadas, ya que podrían ser pasadas por alto por la falta de esta capacidad. Con W^X en su lugar, agregaremos otras mitigaciones complementarias, como CET.‎

‎Apple ha hecho que el W^X sea obligatorio para futuras versiones del sistema operativo de escritorio macOS como parte de la transición de Apple Silicon. Esto empuja a la implementación de esta mitigación para .NET 6, en todos los sistemas operativos compatibles. Nuestro principio es tratar a todos los sistemas operativos compatibles por igual con respecto a la seguridad, siempre que sea posible. W^X está disponible en todos los sistemas operativos con .NET 6, pero solo está habilitado de forma predeterminada en Apple Silicon. Se habilitará en todos los sistemas operativos para .NET 7.‎

‎HTTP/3

‎HTTP/3‎‎ es una nueva versión HTTP. Está en versión preliminar con .NET 6. HTTP/3 resuelve los desafíos funcionales y de rendimiento existentes con versiones HTTP anteriores mediante el uso de un nuevo protocolo de conexión subyacente llamado QUIC. QUIC utiliza UDP y tiene TLS incorporado, por lo que es más rápido establecer conexiones a medida que se produce el protocolo de enlace TLS como parte de la conexión. Cada trama de datos se cifra de forma independiente, por lo que el protocolo ya no tiene el desafío de bloqueo de cabeza de línea en caso de pérdida de paquetes. A diferencia de TCP, una conexión QUIC es independiente de la dirección IP, por lo que los clientes móviles pueden moverse entre redes wifi y celulares, manteniendo la misma conexión lógica, y pueden continuar con descargas largas.‎

‎En este momento, el ‎‎RFC para HTTP / 3‎‎ aún no está finalizado, por lo que aún puede cambiar. Se ha incluido HTTP/3 en .NET 6 para que podemos empezar a experimentar con él. 

‎.NET 6 no incluye compatibilidad con HTTP/3 en macOS, principalmente debido a la falta de una API TLS compatible con QUIC. .NET utiliza SecureTransport en MacOS para su implementación de TLS, que aún no incluye API de TLS para admitir el protocolo de enlace QUIC.‎

‎Cargas de trabajo del SDK‎

‎Las cargas de trabajo del SDK‎‎ son una nueva capacidad que permite agregar nuevas capacidades principales a .NET sin hacer crecer el SDK. Eso es lo que hemos hecho para .NET MAUI, Android, iOS y WebAssembly. No se han medido todas las nuevas cargas de trabajo juntas, pero es fácil adivinar que sumarían al menos el tamaño del SDK tal como está. Sin cargas de trabajo, probablemente no estaría satisfecho con el tamaño del SDK.‎

‎En futuras versiones, tenemos la intención de eliminar más componentes y hacerlos opcionales, incluidos ASP.NET y el escritorio de Windows. Al final, uno puede imaginar que el SDK contiene solo MSBuild, NuGet, los compiladores de lenguaje y la funcionalidad de adquisición de carga de trabajo. Queremos atender a un amplio ecosistema .NET y ofrecer el software que necesita para realizar su trabajo en particular. Puede ver cómo este modelo sería mucho mejor para escenarios de CI, lo que permite a las herramientas dotnet adquirir un conjunto de componentes a medida para el código específico que se está construyendo.‎

Conclusión

‎.NET 6 tiene muchas características y capacidades nuevas, la mayoría de las cuales se han explorado en todas las vistas previas y también en las próximas publicaciones de .NET 6. Esperamos con ansias la siguiente release candidate.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *