Eduard Tomàs presenta sobre temas de memoria, .NET y rendimiento. Explica conceptos como cómo funciona la recolección de basura, las generaciones de memoria y cómo reservar memoria puede afectar el rendimiento de una aplicación. También cubre temas como value types vs reference types, value types con semántica de referencia, Span<T> y stackalloc para mejorar el manejo de la memoria. Recomienda medir el rendimiento y solo optimizar cuando sea necesario.
3. #netcoreconf
¿Quien soy yo?
• Principal Tech Lead @
PlainConcepts BCN
• Padre orgulloso
• Bebedor de cerveza
• Picateclas a mucha honra
• Microsoft MVP desde 2012
4. #netcoreconf
Memoria, .NET y Rendimiento
• La (gran) mayoría de cosas que voy a
contar no las vas a necesitar nunca…
• … Hasta que un día quizá las
necesites.
10. #netcoreconf
¿Por qué?
• Reservar más memoria implica más GC
• GC implica pausas
• Código con menos GC…
• Será más fluído (no necesariamente más rápido)
• Funcionará major en entornos restringidos (batería, cpu,…)
11. #netcoreconf
¿Como funciona el GC?
• Objetos se guardan en el managed heap (memoria manejada)
• Reservar un objeto es rapidísimo
• Garbage Collector libera objetos no usados del managed heap
12. #netcoreconf
GC – Generaciones
• GC no libera todo el heap de golpe. Lo divide en Generaciones (3):
Gen 0 Gen 1 Gen 2
Objetos de corta vida
(Variables locales)
Objetos de vida media Objetos de larga duración
(P. ej. Singletons)
• Los objetos de Gen 0 que están vivos cuando se ejecuta el GC se mueven a
Gen 1
• Los objetos de Gen 1 que están vivos cuando se ejecuta el GC se mueven a
Gen 2
• Los objetos de Gen 2 que están vivos cuando se ejecuta el GC se quedan en
Gen 2
13. #netcoreconf
GC - LOH
• Heap especial para objetos “grandes” > 85Kb
• No compactado (puede provocar OutOfMemoryException)
• Solo se liberan en una “liberación completa” del GC
14. #netcoreconf
GC - ¿Cuando se ejecucta?
• Bueeeenoooo…..
• Cuando no se puede reservar memoria
• Cuando se ha reservado X memoria desde la última ejecución
• Forzado (métodos de System.GC o por Profiler API)
• Aplicación se mueve al background
• No está garantizado cuando se ejecuta el GC
15. #netcoreconf
GC – Tips / Tricks
• GC pausa la aplicación mientras se está ejecutando…
• … excepto en nuevas versiones de .NET (Background GC)
• IDisposable ayuda al GC (limpia referencias)
• Finalizers => Objeto va a la “finalizer queue” y suma una generación
• Usar WeakReference
• Una referencia que no es tenida en cuenta por el GC
16. #netcoreconf
Back to Basics: Value Type vs Reference Type
Reference Type
class
Almacenados en el heap
La variable es una referencia
al objeto en el heap
Semántica de referencia
(asignación y paso implica
copiar la referencia, no el
valor)
Value Type
struct
Almacenados en la pila
La variable es el propio
valor
Semántica de valor
(asignación y paso implica
copiar el valor)
17. #netcoreconf
Reservas de memoria “ocultas”
• params reserva un array con los parámetros
• closures
• LINQ reserva Enumerable
• Iteradores
• async/await
• …
19. #netcoreconf
Value Types o Reference Types
Value Types: Barato reservarlos, caro
pasarlos
Reference Types: Caro reservarlos,
barato pasarlos
¿Podemos tener objetos que sea barato
reservarlos Y barato pasarlos?
20. #netcoreconf
Value Types o Reference Types
Con C# 7.2 SÍ
Podemos tener Value Types con
semántica de referencias
21. #netcoreconf
Value Types con semántica de referencia
• Permite usar value objects como si fuesen reference objects
• Paso por referencia siempre
• Tiene limitaciones (para evitar refs valores inexistentes)
• ref return
• Devuelve un value object por referencia. Modificar la referencia es modificar
el value object original
• ref local
• Una variable que es una referencia a un tipo por valor
• ref local para guardar el resultado de ref return
• Ojo con var! Usar ref var!
22. #netcoreconf
Value Types con semántica de referencia
• ref readonly return
• Permite devolver un value type por referencia
• No permite al receptor, modificar el objeto
• El compilador hace copias automáticamente para asegurar readonly
• Parámetros in
• Permite pasar un value type por referencia (al igual que ref y out)
• El método no puede modificar el parámetro
• readonly struct
• ValueType que el compilador sabe (y obliga a) que es inmutable
• Elimina las copias defensivas del compilador para params in / ref readonly
23. #netcoreconf
Parámetros in
• Se aplican a la firma del método
• Con structs no readonly puede implicar copias defensivas
• Se puede aplicar a la llamada. Si NO se aplica se le da permiso al
compilador para efectuar copias en algunos casos
• Sobrecarga con paso por valor (preferida)
• Existe una conversión implícita del tipo del argumento al tipo del parámetro
• …
24. #netcoreconf
Value Types con semántica de referencia
Todos esos añadidos son puros del
lenguaje C#
El CLR no se ha modificado
25. #netcoreconf
Consejos
• Evita usar in para structs mutables
• Copias defensivas pueden perjudicar el rendimiento
• Usa ref readonly return siempre que puedas
• Usa in para “readonly structs” de tamaño superior a IntPtr.Size
28. #netcoreconf
ref struct
• Una ref struct es una estructura que solo puede ser almacenada en
la pila, nunca en el heap
• NO puede ser miembro de un objeto que esté en el heap.
• NO puede ser miembro de una clase porque estas siempre están en el heap
• NO puede ser miembro de una estructura porque estas están en el heap si a
su vez son miembros de un objeto que esté en el heap (p. ej. una clase)
• Solo se pueden usar como parámetros, valores retorno o variables
locales
30. #netcoreconf
Span<T>
• Unifica el trabajo con “bloques contíguos de memoria”
• Arrays, strings, stackallock, memoria no manejada,…
• Ofrece una API similar a la de un Array
• Segura a nivel de tipos (elementos de tipo T)
• Soporta slicing
• Crear un nuevo Span<T>, subsección de uno existente…
• … sin reservar nueva memoria!
31. #netcoreconf
Span<T> - Dos implementaciones
• Implementación portable
• No require soporte adicional
• Paquete NuGet (netstandard 1.1)
• Netfx 4.5
• Sin ser lento… no es tan rápido como un array
32. #netcoreconf
Span<T> - Dos implementaciones
• Implementación “rápida”
• Requiere soporte del framework
• .NET Core 2.1 o superior
• Optimizaciones JIT propias
• Rendimiento equiparable al de un array
34. #netcoreconf
stackalloc
• Fuerza el almacenamiento en la pila para aquellos elementos que,
habitualmente, se almacenarían en el heap
• Disminuye la presión sobre el GC
• Liberación determinista (al final del método)
37. #netcoreconf
Memory<T>
• Span<T> es “ref struct” => Solo se almacena en la pila
• Variables en métodos async son realmente campos estáticos de una
clase generada por el compilador (incompatible con ref struct)
async Task DoSomethingAsync(Span<byte> buffer) {
buffer[0] = 0;
await Something();
buffer[0] = 1;
}
async Task DoSomethingAsync(Memory<byte> buffer) {
buffer.Span[0] = 0;
await Something();
buffer.Span[0] = 1;
}
38. #netcoreconf
Conclusiones
• Todo eso son micro-optimizaciones
• Úsalas solo si las necesitas. Probablemente puedas optimizar muchas
cosas antes.
• Mide siempre los resultados
• ¡Recuerda que las mediciones dependen de TU máquina y de TU
framework exacto!
• No des nada por supuesto.