5 razones por las que debe dejar de usar System.Drawing en ASP.NET

Hola Habr! Le presento la traducción del artículo "5 razones por las que debe dejar de usar System.Drawing from ASP.NET" .

imagen

Pues lo hicieron. El equipo de corefx finalmente aceptó numerosas solicitudes e incluyó System.Drawing en .NET Core. (artículo original de julio de 2017)

El próximo paquete System.Drawing.Common contendrá la mayor parte de la funcionalidad System.Drawing del .NET Framework completo y está destinado a ser utilizado como una opción de compatibilidad para aquellos que desean migrar a .NET Core pero no pueden hacerlo debido a dependencias. Desde esta perspectiva, Microsoft está haciendo lo correcto. La fricción debe reducirse ya que la adopción de .Net Core es un objetivo más valioso.

Por otro lado, System.Drawing es una de las áreas más pobres y desfavorecidas de .Net Framework y muchos de nosotros esperamos que la implementación de .NET Core signifique la muerte lenta de System.Drawing. Y junto con esta muerte debería ser una oportunidad para hacer algo mejor.

Por ejemplo, el equipo de Mono creó un contenedor compatible con .NET para la biblioteca de gráficos multiplataforma Skia de Google, llamada SkiaSharp . Para simplificar la instalación, Nuget ha recorrido un largo camino en el soporte de bibliotecas nativas para cada plataforma. Skia es bastante completo y su rendimiento hace System.Drawing.

El equipo de ImageSharp también ha hecho un gran trabajo repitiendo gran parte de la funcionalidad de dibujo del sistema, pero con la mejor implementación de API y 100% C #. Todavía no están listos para la explotación productiva, pero parece que ya están lo suficientemente cerca de esto. Una pequeña advertencia sobre esta biblioteca, ya que estamos hablando del uso en aplicaciones de servidor: ahora, en la configuración predeterminada, Parallel.For se usa en el interior para acelerar algunas operaciones, lo que significa que se usarán más flujos de trabajo del grupo ASP.NET, eventualmente Como resultado, se reduce el rendimiento general de la aplicación . Espero que este comportamiento se revise antes del lanzamiento, pero incluso ahora es suficiente cambiar una línea de la configuración para que sea más adecuado para su uso en el servidor.

En cualquier caso, si está dibujando, trazando o renderizando texto en imágenes en una aplicación en el servidor, debería considerar seriamente cambiar System.Drawing a cualquier cosa, independientemente de si cambia a .NET Core o no.

Por mi parte, he reunido una canalización de procesamiento de imágenes de alto rendimiento para .NET y .NET Core, que proporciona una calidad de imagen que System.Drawing no puede proporcionar, y lo hace en una arquitectura altamente escalable diseñada específicamente para su uso en el servidor. Hasta ahora, es solo para Windows, sin embargo, la plataforma cruzada está en los planes. Si usa System.Drawing (u otra cosa) para cambiar el tamaño de las imágenes en el servidor, es mejor considerar MagicScaler como un reemplazo.

Pero es probable que un System.Drawing resurrection, que facilita la transición para algunos desarrolladores, mate la mayor parte del impulso que recibieron estos proyectos, ya que los desarrolladores se vieron obligados a buscar alternativas. Desafortunadamente en el ecosistema .NET, las bibliotecas y paquetes de Microsoft siempre ganarán, sin importar cuán superiores puedan ser las alternativas.

Esta publicación es un intento de corregir algunos errores de cálculo de System.Drawing con la esperanza de que los desarrolladores exploren alternativas incluso si System.Drawing sigue siendo una opción.

Comenzaré con el descargo de responsabilidad frecuentemente citado del Sistema. Dibujo de documentación. Este rechazo se ha planteado un par de veces en una discusión sobre Github cuando se habla de System.Drawing.Common .
“Las clases con el espacio de nombres System.Drawing no son compatibles para su uso en servicios de Windows o ASP.NET. Intentar utilizar estas clases con estos tipos de aplicaciones puede causar problemas inesperados, como una disminución del rendimiento del servidor y errores de tiempo de ejecución ".

Como muchos de ustedes, leí este descargo de responsabilidad hace mucho tiempo, y luego lo omití y aún usé System.Drawing en mi aplicación ASP.NET. Por qué Porque amo el peligro. O eso, o no se encontraron otras opciones viables. ¿Y sabes que? No pasó nada malo. Lo más probable es que no debería haber dicho esto, pero apuesto a que muchos de ustedes han experimentado lo mismo. Entonces, ¿por qué no seguir usando System.Drawing o bibliotecas basadas en él?

Razón # 1: Descriptores GDI


Si alguna vez ha tenido problemas al usar System.Drawing en un servidor, este es probablemente el caso. Si no se prueba, esta es una de las causas más probables.

System.Drawing en su mayor parte es una envoltura delgada alrededor de la API de Windows GDI +. Muchos descriptores de GDI admiten objetos System.Drawing y tienen un límite cuantitativo en el procesador y la sesión del usuario. Si se alcanza este umbral, obtendrá una excepción de "Memoria insuficiente" y / o un error 'genérico' de GDI +.

El problema es que en .NET, la recolección de basura y la finalización del proceso pueden retrasar la publicación de estos descriptores para cuando llegue al límite, incluso con poca carga. Si olvida (o no sabía lo que necesita) llamar a Dispose () en un objeto que contiene dichos descriptores, corre el riesgo de encontrar tales errores en su entorno. Y como la mayoría de los errores relacionados con las limitaciones o fugas de recursos, lo más probable es que esta situación se pruebe con éxito y lo pique en una operación productiva. Naturalmente, esto sucederá cuando su aplicación esté bajo la mayor carga, de modo que el número máximo de usuarios descubra su vergüenza.

Las restricciones en el procesador y en la sesión del usuario dependen de la versión del sistema operativo, y la restricción en el procesador es personalizable. Pero la versión no importa, porque Los descriptores de GDI están representados internamente por el tipo de datos USHORT, por lo que hay un límite estricto de 65.536 descriptores por sesión de usuario, e incluso una aplicación bien escrita corre el riesgo de alcanzar este límite con suficiente carga. Cuando cree que un servidor más potente le permitirá servir a más y más usuarios en paralelo en una instancia, este riesgo se vuelve más real. Y realmente, ¿quién quiere crear software con un conocido límite rígido de escalabilidad?

Razón # 2: concurrencia


GDI + siempre tuvo problemas con la simultaneidad, aunque muchos de ellos estaban relacionados con cambios arquitectónicos en Windows 7 / Windows Server 2008 R2 , todavía se ven algunos de ellos en nuevas versiones. El más notable es el bloqueo del proceso organizado por GDI + durante la operación DrawImage (). Si cambia el tamaño de las imágenes en el servidor utilizando System.Drawing (o las bibliotecas que lo envuelven), el método DrawImage () es probablemente la base de este código.

Además, al realizar varias llamadas a DrawImage () al mismo tiempo, todas se bloquearán hasta que se ejecuten todas. Incluso si el tiempo de respuesta no es un problema para usted (¿por qué no? ¿Odia a sus usuarios?) Tenga en cuenta que los recursos de memoria asociados con estas solicitudes y todos los descriptores GDI que poseen los objetos asociados con estas solicitudes están vinculados al tiempo de ejecución. De hecho, no se necesita mucha carga en el servidor para comenzar a causar problemas.

Por supuesto, hay soluciones para este problema en particular. Por ejemplo, algunos desarrolladores crean un proceso externo para cada operación DrawImage (). Pero, de hecho, esa solución solo agrega fragilidad adicional, lo que realmente no debería haber hecho.

Razón # 3: Memoria


Considere un controlador ASP.NET que genera un gráfico. Debería hacer algo como esto:

  1. Crear un mapa de bits como un lienzo
  2. Dibuja múltiples formas en un mapa de bits usando bolígrafos y / o pinceles
  3. Dibuja texto usando una o más fuentes
  4. Guardar mapa de bits como PNG en MemoryStream

Digamos que el gráfico mide 600 por 400 puntos. Esto es un total de 240,000 puntos, multiplicado por 4 bytes para un punto para el formato RGBA predeterminado, totalizando 960,000 bytes para un mapa de bits, más un poco para dibujar objetos y un búfer de guardado. Deje que sea 1mb para toda la solicitud. Lo más probable es que no tenga problemas de memoria para tal escenario, y si se encuentra con algo, lo más probable es que tenga un límite en el número de descriptores, que mencioné anteriormente, ya que las imágenes, los pinceles, los lápices y las fuentes tienen sus propios descriptores.

El verdadero problema surge cuando System.Drawing se usa para tareas de imágenes. System.Drawing es principalmente una biblioteca de gráficos, y las bibliotecas de gráficos generalmente se basan en la idea de que todo es un mapa de bits en la memoria. Esto es genial mientras piensas en las pequeñas cosas. Pero las imágenes pueden ser realmente grandes, y se hacen más grandes todos los días, porque Las cámaras con muchos megapíxeles son cada vez más baratas.

Si adopta el enfoque ingenuo de System.Drawing para la creación de imágenes, obtendrá algo como esto para el controlador de cambio de tamaño:

  1. Cree un mapa de bits como lienzo para la imagen de destino.
  2. Cargue la imagen original en otro mapa de bits.
  3. Llame a DrawImage () con el parámetro "image-source" para la imagen de destino, utilizando el cambio de tamaño.
  4. Guarde el mapa de bits de destino en formato JPEG en la secuencia de memoria.

Supongamos que la imagen de destino será 600x400, como en el ejemplo anterior, luego nuevamente tenemos 1 MB para la imagen de destino y el flujo de memoria. Pero supongamos que alguien cargó una imagen de 24 megapíxeles de sus nuevas réflex digitales, entonces necesitamos 6000x4000 píxeles con 3 bytes para cada uno (72 MB) para el mapa de bits de la fuente decodificada en formato RGB. Y utilizaremos el remuestreo HighQualityBicubic de System.Drawing, porque solo se ve bien. Luego, debemos tener en cuenta los otros 6000x4000 puntos con 4 bytes cada uno, para la conversión PRGBA que ocurre dentro del método llamado , agregando 96mb adicionales de memoria usada. En total, se obtienen 169mb (!) Por una solicitud para convertir una sola imagen.

Ahora imagine que tiene más de un usuario haciendo tales cosas. Ahora recuerde que las solicitudes se bloquean hasta que todas se ejecuten por completo. ¿Cuánto tiempo lleva quedarse sin memoria? E incluso si no se preocupa de agotar por completo todo lo disponible, recuerde que hay muchas maneras de utilizar mejor la memoria de su servidor que contener un montón de píxeles. Considere el efecto de la presión de la memoria en otras partes de la aplicación / sistema:

  1. La memoria caché de ASP.NET puede comenzar a vaciar elementos que son costosos de recrear
  2. El recolector de basura se iniciará con más frecuencia, ralentizando la aplicación
  3. El caché del kernel IIS o el caché del sistema de archivos de Windows puede eliminar elementos útiles
  4. El grupo de aplicaciones puede exceder el límite de memoria y puede reiniciarse
  5. Windows puede comenzar a intercambiar memoria al disco, ralentizando todo el sistema

¿Realmente no quieres nada de esto?

Las bibliotecas diseñadas específicamente para tareas de procesamiento de imágenes abordan este problema de una manera completamente diferente. No necesitan cargar toda la fuente o la imagen de destino en la memoria. Si no va a dibujar en él, no necesita un lienzo / mapa de bits. Esto se hace más así:

  1. Crear flujo para el codificador JPEG de la imagen de destino
  2. Cargue una línea de la imagen original y comprímala horizontalmente
  3. Repita tantas veces como sea necesario para formar una línea para el archivo de destino
  4. Comprime las líneas resultantes verticalmente
  5. Repita desde el paso 2 hasta que se procesen todas las líneas del archivo fuente.

Con este método, la misma imagen se puede procesar con 1 MB de memoria en total, e incluso las imágenes mucho más grandes requerirán un ligero aumento en la sobrecarga.

Conozco solo una biblioteca .NET que está optimizada por este principio, y le daré una pista: esto no es System.Drawing.

Razón # 4: CPU


Otro efecto secundario de System.Drawing que está más orientado gráficamente que orientado a imágenes es que DrawImage () es bastante ineficiente en términos de uso de CPU. Cubrí esto con cierto detalle en una publicación anterior , pero esta discusión se puede resumir en los siguientes hechos:

  • En System.Drawing, la conversión de escala HighQualityBicubic solo funciona con el formato PRGBA. En casi todos los escenarios, esto significa una copia extra de la imagen. Esto no solo usa (significativamente) más memoria adicional, sino que también quema los ciclos del procesador para convertir y procesar el canal alfa adicional.
  • Incluso después de que la imagen esté en su formato nativo, la conversión de escala HighQualityBicubic realiza aproximadamente 4 veces más cálculos de los necesarios para obtener los resultados de conversión correctos.

Estos hechos agregan una cantidad significativa de ciclos de CPU desperdiciados. En un entorno nublado con pago por minuto, esto contribuye directamente al costo del alojamiento. Y, por supuesto, su tiempo de respuesta se verá afectado.

Y piense en el hecho de que se gastará electricidad adicional y se generará calor. Su uso de System.Drawing para tareas de procesamiento de imágenes afecta directamente el calentamiento global. Eres un monstruo

Razón 5: el procesamiento de imágenes es engañosamente complejo


Dejando a un lado el rendimiento, System.Drawing de muchas maneras evita que la imagen se procese correctamente. Usar System.Drawing significa vivir con resultados incorrectos o aprender todo sobre el perfil ICC, la cuantización del color, la orientación exif, la corrección y muchas otras cosas específicas. Este es un agujero de conejo, que la mayoría de los desarrolladores no tienen ni el tiempo ni el deseo de explorar.

Las bibliotecas como ImageResizer e ImageProcessor han ganado muchos admiradores, cuidando algunos de estos detalles, pero tenga cuidado, tienen System.Drawing dentro, y vienen con todo el equipaje que describí en detalle en este artículo.

Razón adicional: puedes hacerlo mejor


Si usted, como yo, tuvo que usar anteojos en algún momento de su vida, probablemente recuerde cómo fue la primera vez que se los puso. Pensé que estaba viendo normalmente, y si entrecerro los ojos correctamente, entonces todo estará bastante claro. Pero luego me puse estas gafas y el mundo se volvió mucho más detallado de lo que podría haber imaginado.

Sistema: el dibujo es muy parecido. Hace lo correcto si completa la configuración correctamente , pero se sorprenderá de lo mucho que se verán sus imágenes si usa las mejores utilidades.

Dejaré esto aquí como ejemplo. Este es el mejor sistema que se puede hacer en comparación con la configuración predeterminada de MagicScaler. Tal vez su aplicación se beneficiará al obtener puntos ...

Gdi:

imagen

MagicScaler:

imagen
Foto de Jakob Owens.

Eche un vistazo, explore alternativas y, en nombre del amor por los gatitos, deje de usar System.Drawing en ASP.NET

Source: https://habr.com/ru/post/482274/


All Articles