Ir al contenido
learn

C# avanzado — Misión

Llevo años escribiendo C#, y la mayor parte de lo que sé sobre su rendimiento es folclore: «los structs son más rápidos», «evita LINQ», «usa siempre ConfigureAwait(false)», «FrozenDictionary es el rápido». Parte de ello era cierto en .NET Framework 4.5 y es falso en .NET 10, donde el JIT elimina asignaciones que el IL pide. Este curso sustituye cada pieza de folclore por algo que puedo observar: el tamaño de un objeto, el IL que emitió el compilador, la máquina de estados que hay detrás de await, la generación de un array, una tabla de BenchmarkDotNet.

Lo mismo vale para el código que mueve datos entre tareas y para ASP.NET Core. Uso canales y BackgroundService sin haber comprobado qué pasa cuando un consumidor se detiene antes de tiempo o un productor lanza una excepción, y configuro ASP.NET Core copiando lo que funcionó la última vez. Las partes posteriores del curso convierten también esas preguntas en programas.

Las mediciones se hacen sobre código real, no sobre clases de juguete: Guitar Alchemist (GA), una gran base de código .NET 10 sobre teoría musical. Sus objetos de valor, sus cachés, su generador de voicings y sus servicios hospedados son justo el tipo de código donde surgen estas preguntas, y las lecciones encontraron varios lugares donde GA paga por algo que no pretendía, o se bloquea donde debería fallar.

Escribes C# a diario y conoces bien el lenguaje: genéricos, LINQ, async/await, records, coincidencia de patrones. Quieres saber qué hacen el compilador, el JIT, el recolector de basura y ASP.NET Core con ese código, y medir antes de optimizar. Si estás empezando con C#, comienza por el curso C# para principiantes, que termina donde empieza este.

Si también trabajas con Java, las partes 2 y 3 terminan cada lección con una tabla «Si conoces Spring y Reactor», de C# a Java. El curso de Spring Boot, Spring Cloud y Reactor va en sentido contrario, de ASP.NET Core a Spring; los dos cursos se enlazan entre sí en lugar de explicar dos veces lo mismo.

  • predecir el tamaño de un valor o de un objeto, detectar el boxing en el IL y saber cuándo lo elimina el JIT;
  • usar ref, in, ref readonly, Span<T> y stackalloc sin copias defensivas ni referencias que escapan;
  • explicar las generaciones, los montones de objetos grandes y de objetos fijados y los modos del GC, y leer GC.GetGCMemoryInfo;
  • leer la máquina de estados que el compilador genera para un método async, elegir entre Task y ValueTask, y evitar interbloqueos y cancelaciones perdidas;
  • escribir un benchmark de BenchmarkDotNet que mida lo que creo que mide, e interpretar la compilación por niveles y la PGO;
  • elegir entre Dictionary, FrozenDictionary, SearchValues y la aritmética simple, y vectorizar un bucle con Vector<T> o TensorPrimitives;
  • conectar productores y consumidores con canales, TPL Dataflow, Rx.NET o IAsyncEnumerable, y decir para cada uno qué ocurre con backpressure, ante un error y ante una cancelación;
  • seguir una petición a través de ASP.NET Core, de Kestrel al endpoint, y elegir con criterio las duraciones de los servicios, las opciones, los filtros, los servicios hospedados, la resiliencia y la caché;
  • observar y probar un servicio ASP.NET Core, y publicarlo con Native AOT;
  • y, en la última parte: árboles de expresión, generadores de código fuente, analizadores de Roslyn e interoperabilidad.

Las partes 1 y 2 miden el propio código de GA. La parte 3 construye un pequeño servicio de escalas y acordes sobre los tipos de dominio de GA, el equivalente en ASP.NET Core del servicio de escalas del curso de Spring Boot, Spring Cloud y Reactor: los mismos endpoints, para que cada lección pueda comparar las dos implementaciones línea por línea.

# Lección Bajo el capó Medido en GA Spring y Reactor
1 Memoria: valores, referencias y spans disposición de los objetos, boxing en el IL, ref/in, ref struct, Span<T>, stackalloc PitchClass, PitchClassSetId.ItemsSpan sin tipos por valor, genéricos borrados, liberar un DataBuffer
2 El recolector de basura generaciones, LOH y POH, GC de estación de trabajo y de servidor, DATAS, finalizadores, GC.GetGCMemoryInfo asignaciones de ItemsSpan con [MemoryDiagnoser] G1 y ZGC, Cleaner, el -prof gc de JMH
3 async y await bajo el capó la máquina de estados generada, ValueTask, SynchronizationContext, ConfigureAwait, cancelación, IAsyncEnumerable Try.OfAsync, LazyWithExpiration ensamblado y suscripción, publishOn, hilos virtuales
4 Rendimiento medido BenchmarkDotNet, JIT por niveles y PGO, SearchValues, FrozenDictionary, Vector<T> resta de PitchClass, SimdOps.Dot JMH, C1 y C2, la API Vector
5 Genéricos en profundidad restricciones, miembros abstractos estáticos, matemáticas genéricas, allows ref struct, cómo comparte el JIT el código genérico IStaticValueObjectList<TSelf> de GA el borrado, y genéricos sin primitivos
# Lección Bajo el capó Spring y Reactor
6 Channels canales acotados y no acotados, modos de canal lleno, finalización y errores, varios productores y consumidores, cancelación; el generador de voicings y el comando de indexación de GA onBackpressureBuffer, onBackpressureDrop, BlockingQueue
7 TPL Dataflow bloques y enlaces, paralelismo y orden, BoundedCapacity, errores que solo bajan, finalización; la demo de Dataflow de GA flatMap con concurrencia, buffer, publishOn
8 Rx.NET IObservable<T>, frío y caliente, operadores en tiempo virtual, schedulers, sin backpressure, reintentos; la demo reactiva de GA Flux, Sinks, publishOn, StepVerifier.withVirtualTime
9 Elegir un flujo IAsyncEnumerable y System.Linq.AsyncEnumerable, los cuatro tipos de flujo medidos lado a lado, puentes, rendimiento, un diagrama de decisión Reactor: Mono y Flux, Reactor por dentro
10 Estado compartido y el grupo de subprocesos System.Threading.Lock, Interlocked, colecciones concurrentes, Parallel.ForEachAsync, inanición del grupo de subprocesos synchronized, ReentrantLock, hilos virtuales
# Lección Bajo el capó Spring y Reactor
11 Hospedaje, WebApplication y Kestrel el host genérico, el builder, los límites de conexión y de petición de Kestrel, el apagado ordenado Spring Boot visto desde ASP.NET Core, Tomcat y Netty embebidos
12 El pipeline de middlewares Use, Map, Run, orden, cortocircuitos, manejo de excepciones, enrutamiento de endpoints filtros Servlet, WebFilter
13 Inyección de dependencias y opciones duraciones, dependencias cautivas, validación de ámbitos, servicios con clave, IOptions, IOptionsSnapshot, IOptionsMonitor, validación el contenedor de Spring, @ConfigurationProperties
14 Minimal APIs y controladores manejadores de rutas y enlace de parámetros, filtros, validación, TypedResults, respuestas IAsyncEnumerable en streaming @RestController, endpoints funcionales de WebFlux
15 Servicios hospedados IHostedService, BackgroundService, orden de arranque y de apagado, excepciones, colas con canales; los servicios hospedados de GA @Scheduled, SmartLifecycle
16 Autenticación y autorización esquemas, manejadores, JWT bearer, directivas y requisitos Spring Security
17 Oráculos de especificación Petri para pipelines C# modelos finitos de ciclo de vida, alcanzabilidad completa, clasificación terminal, compuertas de ejecución deterministas; colas Channel, ejecución/salidas retenidas de Dataflow y supuestos Rx mantenidos por separado el mismo método se aplica a Reactor solo después de modelar explícitamente su capacidad y sus schedulers
18 gRPC y SignalR contratos protobuf, llamadas en streaming, hubs, backpressure a través de la red Spring gRPC, WebSocket, RSocket
19 Resiliencia, limitación de velocidad y caché de salida Microsoft.Extensions.Http.Resilience, pipelines de Polly, limitadores de velocidad, directivas de caché de salida Resilience4j, Spring Cloud Circuit Breaker
20 OpenTelemetry y diagnóstico en producción System.Diagnostics.Metrics, ActivitySource, exportadores de OpenTelemetry, dotnet-counters, dotnet-trace, dotnet-dump Micrometer, Actuator
21 Pruebas con WebApplicationFactory el host de pruebas, sustituir servicios, autenticación en las pruebas, Testcontainers @SpringBootTest, WebTestClient
22 Native AOT y recorte publicar una API con Native AOT, advertencias de recorte, el generador de delegados de solicitud, arranque y tamaño medidos imágenes nativas de GraalVM
# Lección Bajo el capó Spring y Reactor
23 Árboles de expresión, reflexión y generadores de código fuente en qué se compila una lambda, Expression<T>, el coste de la reflexión, generadores incrementales, [GeneratedRegex] procesadores de anotaciones, el motor AOT de Spring
24 Analizadores y correcciones de código de Roslyn modelos sintácticos y semánticos, escribir un analizador y sus pruebas Error Prone, SpotBugs
25 Interoperabilidad y código no seguro [LibraryImport], punteros de función, Unsafe, MemoryMarshal, fijación JNI, y la API de funciones y memoria externas
# Página Bajo el capó Medido sobre GA
1 Cinco optimizaciones, demostradas y luego medidas rotaciones y PopCount sobre conjuntos de 12 bits, tablas de consulta, una demostración de equivalencia sobre todo el dominio de entrada, los ordenamientos estables como desempate, y un benchmark que medía el JIT en lugar del código IsClusterFree, IntervalClassVector, ClosestDiatonicKey, ToNormalForm, PrimeForm
2 GA, perfilado, luego demostrado y medido dotnet-trace sobre un pipeline real, máscaras de 12 bits en lugar de conjuntos hash, enumeradores encapsulados, una caché de 4096 casillas indexada por el propio conjunto, una propiedad LINQ en un bucle caliente, y una demostración que compara byte a byte dos compilaciones de GA CanonicalChordRecognizer, ChordIntervalPattern.TryMatch, IntervalClassVector, OptickIndexReader
3 Oráculo de ciclo de vida con redes de Petri un pipeline acotado con éxito, fallo y cancelación explícitos; alcanzabilidad completa y ningún marcado muerto no terminal las formas de fallo de Channel estudiadas en las lecciones 6 y 9
4 Plugins sin MEF un AssemblyLoadContext descargable por módulo, AssemblyDependencyResolver, un único contenedor de inyección de dependencias, dos versiones de una biblioteca lado a lado, la identidad de los tipos, y un temporizador que mantiene cargado su módulo no GA: cuatro módulos pequeños escritos para el apéndice
— Diario

Las lecciones 18 a 25 están planificadas y aún no se han escrito. La lección 17 es un laboratorio avanzado de concurrencia añadido después de las lecciones de ASP.NET Core; las lecciones 6 a 9 se escribieron antes que la lección 5 y no dependen de ella.

  • El SDK de .NET 10 y Git. En Windows, ejecuta los scripts del curso desde Git Bash.
  • check.sh restaura ilspycmd como herramienta local de .NET (versión 11.0.0.9375, fijada en .config/dotnet-tools.json), descarga los tres proyectos de GA que usa el programa, unos 11 MB, y restaura System.Reactive 7.0.0 para la lección 8, y Newtonsoft.Json 13.0.3 y 13.0.1 para el apéndice 4.
  • Para los benchmarks, una máquina que puedas mantener tranquila durante unos minutos: cierra el navegador y enchufa el portátil.