¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Escribir código .NET eficaz
  3. Perfilar una aplicación .NET
Extrait - Escribir código .NET eficaz Perfilado, benchmarking y buenas prácticas
Extractos del libro
Escribir código .NET eficaz Perfilado, benchmarking y buenas prácticas Volver a la página de compra del libro

Perfilar una aplicación .NET

Gestión de la memoria de la parte de .NET

1. Principios de base

La plataforma .NET incluye un gestor de memoria encargado de la asignación y liberación de memoria. Estas dos operaciones funcionan juntas, por supuesto, pero de una manera muy diferente a C++ u otros lenguajes donde la memoria es gestionada manualmente por el desarrollador.

En .NET, en lugar de dejar al desarrollador la gestión de estas dos operaciones, el motor de ejecución (runtime) toma a su cargo la totalidad de la segunda operación, es decir, la limpieza de la memoria. El desarrollador reserva la memoria creando una instancia de una clase con la palabra clave new, pero no está obligado a liberarla explícitamente después (aunque todavía es posible configurar mecanismos avanzados de limpieza de la memoria). Esta es la función de un módulo de .NET llamado Recolector de Basura (o Garbage Collector en inglés, a veces abreviado como GC). Este último se encargará, cuando sea necesario, de recolectar la memoria a partir de ahora inútil y volverla a poner a disposición del programa. En los siguientes capítulos explicaremos su funcionamiento en detalle.

La mayoría de los desarrolladores de .NET no prestan atención a cómo se gestiona la memoria. Este es un gran logro de la plataforma, porque si es tan transparente, es porque el proceso es eficiente. No obstante, conocer el funcionamiento de esta gestión permite, en los casos más avanzados, ser capaz de responder y hacer frente a posibles problemas, que detallaremos más a medida que avance el capítulo.

2. Gestión automatizada de memoria y rendimiento

Como todas las técnicas que simplifican el trabajo de programación, ha habido cierta controversia sobre la pérdida de rendimiento asociada al uso del recolector de basura. Para acortar una discusión potencialmente estéril, volvamos simplemente a los principios de mejora del rendimiento de una aplicación, tal como se expone al principio de este libro. De hecho, gestionar la memoria manualmente puede permitir ahorrar un poco del tiempo invertido en la ejecución dentro de una funcionalidad. Pero la implementación de la gestión manual es compleja: incluso los desarrolladores experimentados cometen errores de gestión de memoria en C++, y esta...

Particularidades de las funciones en línea

1. Mecanismo de las funciones en línea

Una característica de .NET, que también está presente en otros lenguajes, es la noción de función en línea (inline en inglés). Este mecanismo se utiliza especialmente en forma de atributo que se coloca encima de un método. Cuando se llama a este método, en lugar de realizar un salto en la memoria a la ubicación del código máquina correspondiente, .NET copia la porción de código en cuestión directamente a la ubicación de la llamada a la función. Por supuesto, esta optimización solo se activa cuando el código en cuestión es muy pequeño (en este caso, la documentación indica que el tamaño máximo del código IL es de 16 bytes).

El ejemplo siguiente muestra dos funciones, Funcion1 y Funcion2, que reproducen el mismo cálculo de dos maneras distintas. La primera calcula el resultado deseado directamente, mientras que la segunda pasa por una función intermedia.

using System;  
  
namespace FuncionEnlinea  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            int resultado = Funcion1(1);  
            resultado = Funcion2(1);  
        }  
  
        private static int Funcion1(int Parametro)  
        {  
            int Resultado = Parametro * 2;  
            return Resultado + 1;  
        }  
  
        private static int Funcion2(int v)  
        {  
            int Resultado = FuncionIntermedia(Parametro);  
            return Resultado...

Impacto de la gestión de la memoria en el rendimiento

1. Una gran diversidad en los impactos

Ahora que se han establecido todos estos conceptos, ¿cuáles son los impactos de la gestión de la memoria en el rendimiento de una aplicación? Son múltiples.

Evidentemente, el principal impacto de un mal rendimiento de una aplicación es el de una memoria sobrecargada: cuanto mayor sea la cantidad de memoria utilizada, más tiempo tardará el sistema en gestionarla y, sobre todo, cuando se alcancen los límites de la memoria física, el sistema verá deteriorado su rendimiento de forma acusada, llegando incluso a negarse a responder y a ejecutar una excepción que significa que no hay más memoria disponible (System.OutOfMemoryException). 

Pero también se pueden encontrar impactos a largo plazo. Típicamente, recursos que no se liberan y que poco a poco abarrotan la memoria hasta que se saturan. 

Por último, podemos constatar la lentitud o una memoria demasiado saturada simplemente por la fragmentación. En definitiva, toda una serie de impactos muy diversos que detallaremos en los próximos apartados.

2. Uso de la memoria virtual

Desde un punto de vista estrictamente cuantitativo, una ocupación excesiva de la memoria ralentiza el rendimiento de todo el sistema, ya que éste se ve obligado a utilizar la memoria virtual y, por tanto, depende en parte de la velocidad de almacenamiento en lugar de mantener todo en la RAM. El impacto en estos casos puede ser muy significativo, especialmente cuando se utiliza un almacenamiento secundario mecánico.

Esto ocurre mucho antes de obtener una OutOfMemoryException. En la mayoría de los sistemas operativos (SO), un programa obtiene un espacio de memoria que parece ser contiguo, pero el SO hace un mapeo a la RAM o al espacio conocido como swap (memoria de intercambio) en segundo plano. Este swap es una reserva de memoria muy lenta en comparación con la RAM, pero al menos existe. Esta memoria es lenta porque no está conectada físicamente a la RAM, sino que se encuentra en partes de un disco duro o SSD (unidad de estado sólido). Sin embargo, permite utilizar más memoria para todos los programas que la realmente disponible en la RAM, al trasladar las páginas menos utilizadas al disco.

Las excepciones del tipo OutOfMemory se producen...

Otros recursos que hay que vigilar

1. La memoria no lo es todo

En las secciones anteriores se ha explicado cómo el consumo de memoria puede afectar al rendimiento de una aplicación .NET. Aunque los problemas relacionados con la RAM son una de las principales fuentes de ralentización, no son ni mucho menos los únicos. Todos los recursos de una máquina, o de un sistema, pueden ser cuellos de botella.

2. La CPU

En primer lugar, está el consumo del procesador. Este es el factor limitante más común en el rendimiento de una aplicación. También es el criterio más difícil de estudiar. En el siguiente gráfico, ¿se utiliza correctamente la CPU?

images/cap03_img54.png

Observamos, en primer lugar, que se están utilizando ambos núcleos, lo que es un signo de una aplicación multihilo. Aunque esto es ahora una práctica estándar para el software destinado a una amplia distribución, todavía está lejos de ser un comportamiento garantizado en los programas creados a medida para una compañía por una empresa de consultoría, o incluso por ciertos editores de software. La democratización del modo de programación asíncrono ha contribuido en gran medida a hacer más accesible el desarrollo de aplicaciones multihilo. Sin embargo, todavía hay aplicaciones que no aprovechan las capacidades del hardware en todo su potencial. Por no mencionar que la facilidad de crear código asíncrono también puede crear una ocupación innecesaria de la CPU que podría haberse utilizado en otras tareas.

En segundo lugar, ¿qué pasa con el hecho de que el consumo de la CPU es entrecortado, y de una media inferior a la mitad de lo disponible? ¿Es un punto a favor de la aplicación, es decir, que es capaz de arreglárselas con poca CPU? ¿O es razonable otro punto de vista, a saber, considerar que no está aprovechando todos los recursos disponibles?

La respuesta depende del contexto. Tomemos un segundo ejemplo:

images/cap03_img55a.png

Esta vez observamos que el procesador se ha utilizado al 100 % de su capacidad durante un periodo significativo. Un desarrollador diría que la aplicación está funcionando «a tope». De nuevo, ¿es bueno o malo?

El contexto de uso es fundamental para determinar si el consumo...