2 puntos por GN⁺ 2024-10-20 | 1 comentarios | Compartir por WhatsApp

-.NET 9.0 reduce significativamente el tiempo de ejecución frente a .NET 8 en varios escenarios comunes de LINQ y, en algunos benchmarks, incluso elimina las asignaciones

  • Una de las mejoras clave es obtener un ReadOnlySpan<T> con TryGetSpan() al recorrer arreglos o List<T>, para reducir el costo de iteración
  • TryGetSpan() identifica TSource[] y List<TSource> mediante comparación de tipos, pero la forma de tomar un span del arreglo interno de List<T> es una optimización de la familia Unsafe que puede invalidarse si cambia la capacidad
  • El LINQ de .NET 9 reconoce cadenas de llamadas comunes y crea iterators especiales, además de aplicar optimizaciones adicionales en métodos terminales como Count(), First(), Last(), ElementAt() y Sum()
  • Con solo migrar y recompilar ya se pueden obtener algunas mejoras de rendimiento de LINQ, y también se incluyen optimizaciones como uso de SIMD y detección temprana de secuencias vacías

Por qué ahora es más rápido recorrer arreglos y listas

  • El primer benchmark guarda Enumerable.Range(1, 10_000).ToArray() como IEnumerable<int> y luego ejecuta Count, All, Any, First, Single y Last para comparar .NET 8 con .NET 9
  • Se usa BenchmarkDotNet, y el proyecto debe apuntar a net8.0;net9.0 y compilarse en modo Release
  • En .NET 9 el tiempo de ejecución de varios métodos baja mucho y también desaparecen las asignaciones
    • LinqCount: de 16,198.490 ns a 3,043.563 ns, de 32 B asignados a ninguna asignación
    • LinqAny: de 17,096.735 ns a 2,483.927 ns, de 32 B asignados a ninguna asignación
    • LinqFirst: de 15,289.747 ns a 2,243.341 ns, de 32 B asignados a ninguna asignación
    • LinqSingle: de 21,684.114 ns a 4,884.329 ns, de 32 B asignados a ninguna asignación
    • LinqAll: de 10.588 ns a 2.562 ns, de 32 B asignados a ninguna asignación
    • LinqLast: de 15.967 ns a 6.918 ns

La diferencia que genera TryGetSpan()

  • La causa principal de la mejora de rendimiento es el uso de TryGetSpan()
  • Si el enumerable que se recorre es un arreglo o una lista, TryGetSpan() devuelve un ReadOnlySpan<T> que permite iterar más rápido
  • El código de bifurcación clave comprueba source.GetType() == typeof(TSource[]) o source.GetType() == typeof(List<TSource>) y luego obtiene el span
    • Los arreglos se manejan con Unsafe.As<TSource[]>(source)
    • Las listas usan CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) para obtener un span desde el arreglo interno
  • En el código source.GetType() se llama dos veces y no se resuelve con casting más verificación de null, pero es una decisión tomada por expertos en rendimiento de .NET considerando las optimizaciones del compilador de C# y del JIT
  • En el stack de .NET altamente optimizado, las microoptimizaciones pueden dar resultados distintos a lo que aparentan

Restricciones de CollectionsMarshal.AsSpan()

  • List<TSource> internamente referencia un arreglo, y cuando la capacidad de la lista debe crecer o reducirse crea un arreglo nuevo y pasa a referenciarlo
  • CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) obtiene un Span<TSource> desde ese arreglo interno
  • Si la capacidad de la lista cambia de cualquier manera, el arreglo obtenido por este método puede invalidarse
  • Por esta limitación, algunas operaciones de Enumerable que incluyen iteración diferida, como yield, no pueden depender fácilmente de esta optimización
  • El propio nombre System.Runtime.CompilerServices.Unsafe ya deja ver ese riesgo

Alcance de las llamadas a TryGetSpan()

  • Con NDepend se escaneó System.Linq.dll para verificar los llamadores directos e indirectos de TryGetSpan()
  • La ruta del ensamblado analizado es C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll
  • Desde TryGetSpan() se generó una consulta de código para identificar llamadores y se exportaron 56 métodos coincidentes a un grafo de dependencias
  • Muchos métodos estándar de Enumerable intentan hacer recorrido por span cuando la colección es un arreglo o una lista
  • Aun así, como tomar el arreglo interno de una lista no es seguro, siguen existiendo límites en operaciones que requieren ejecución diferida

Optimización basada en iterators especiales

  • El segundo benchmark usa un caso del PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
  • Entre los casos probados están Distinct().First(), Append().Select().Last(), Reverse().Count(), DefaultIfEmpty().Select().ElementAt(), Skip().Take().ElementAt(), Union().First() y Select().Where().Select().Sum()
  • En .NET 9 algunas cadenas de llamadas se vuelven extremadamente rápidas
    • DistinctFirst: de 65.318 ns a 11.192 ns, de 328 B asignados a ninguna asignación
    • AppendSelectLast: de 4,122.007 ns a 2.661 ns, de 144 B asignados a ninguna asignación
    • DefaultIfEmptySelectElementAt: de 4,090.818 ns a 5.724 ns, de 144 B asignados a ninguna asignación
    • RangeUnionFirst: de 66.309 ns a 6.193 ns, de 344 B asignados a ninguna asignación
    • ListSkipTakeElementAt: de 6.268 ns a 2.916 ns
    • RangeReverseCount: de 11.024 ns a 6.134 ns
  • En cambio, SelectWhereSelectSum se volvió más lento: pasó de 3,959.622 ns en .NET 8 a 4,460.008 ns en .NET 9, y además mantuvo la asignación de 112 B

Reconocimiento de cadenas comunes de LINQ

  • El equipo de rendimiento de .NET diseñó el código para reconocer cadenas comunes de llamadas LINQ
  • Cuando detecta una cadena específica, crea un iterator especial que procesa el flujo de trabajo de manera más eficiente
  • Si la cadena termina en métodos como Count(), First(), Last(), ElementAt() o Sum(), se pueden aplicar optimizaciones adicionales
  • Por ejemplo, OrderBy(criteria).First() puede optimizarse para ejecutarse como Min(criteria)

Estructura de Iterator<T> y sus clases derivadas

  • Dentro de LINQ existe la clase base abstracta Iterator<T> y 40 clases derivadas
  • Todas estas clases están anidadas dentro de la clase Enumerable
  • Iterator<T> es una clase abstracta, pero sus métodos son virtuales, así que las clases derivadas solo sobrescriben los métodos que necesitan
  • Esta estructura sirve como base para encapsular comportamientos especiales según cada cadena de llamadas

Caso de ListWhereSelectIterator<TSource, TResult>

  • ListWhereSelectIterator<TSource, TResult> procesa la cadena Where(...).Select(...) sobre listas en un solo iterator
  • Este iterator se crea desde el override de Select() de ListWhereIterator<TSource, TResult>
  • ListWhereIterator<TSource> se crea cuando Enumerable.Where() verifica que la fuente sea List<TSource>
  • ListWhereSelectIterator<TSource, TResult> no sobrescribe métodos como TryGetFirst() o TryGetLast()
  • La clave de la mejora de rendimiento es que la cadena tan común Where(...).Select(...) sobre listas se fusiona en un solo iterator, en lugar de usar dos
    • Dentro de MoveNext() se invocan juntos los dos delegates _predicate y _selector

Caso de IListSkipTakeIterator<TSource>

  • IListSkipTakeIterator<TSource> es un iterator especial que se crea cuando aplica
  • MoveNext() usa _state - 1 como índice base 0 de la lista
  • Tener un campo de índice separado sería más fácil de leer, pero para reducir el tamaño de los campos del iterator se almacena con bias dentro de _state
  • La optimización de este iterator consiste en no recorrer innecesariamente elementos fuera del rango _minIndexInclusive y _maxIndexInclusive

Optimizaciones adicionales que se obtienen solo con migrar

  • En .NET 9 varios escenarios comunes de LINQ se vuelven más rápidos
  • Para aprovechar las mejoras de la nueva versión de .NET, lo único necesario es migrar y recompilar
  • LINQ también se optimiza de otras formas
    • En casos como la suma de secuencias de enteros, usa SIMD cuando es posible
    • Las secuencias vacías se detectan antes, reduciendo el costo de enumeración
  • DeepDotnet videos puede verse como material de aprendizaje de .NET con Scott Hanselman y Stephen Toub

1 comentarios

 
GN⁺ 2024-10-20
Opiniones de Hacker News
  • Creo que la parte más útil de LINQ no es la estructura extensible basada en árboles de sintaxis de IQueryable, ni la sintaxis integrada en el lenguaje, sino los métodos de extensión de IEnumerable
    Antes se le llamaba, de forma algo confusa, “LINQ to Objects”, y permite escribir C# de manera concisa con un estilo funcional
    El artículo original trata principalmente sobre la optimización de estos métodos de extensión
    Recién después de aprender Haskell entendí bien este enfoque, y también comparte algunas de las ventajas y trampas de Haskell, como la evaluación diferida
    Si se usa sin criterio, puede terminar en código difícil de entender y lento, así que no lo recomendaría si en el equipo no hay alguien que conozca los modismos funcionales básicos y la evaluación diferida

    • Yo también prefiero el aspecto funcional de las extensiones LINQ sobre IEnumerable e IQueryable
      Es más fácil razonar sobre ellas y, en lugares como Entity Framework, no siempre son la opción más rápida, pero en general son una alternativa bastante buena
      También me gusta usar Dapper más que EF
      Dicho eso, los proyectos en C# tienden a acumular una cantidad absurda de capas de abstracción, y el desarrollo “enterprise” suele ser bastante doloroso de ver
    • Yo también uso LINQ así
      Hay algunos nombres un poco poco estándar, pero tiene todo lo necesario
      Eric Lippert escribió una excelente serie de artículos explicando las mónadas en relación con LINQ: https://ericlippert.com/2013/04/02/monads-part-twelve/
    • Siempre he usado LINQ solo con sintaxis de métodos
      No me gusta que haya otro lenguaje “integrado” dentro del lenguaje anfitrión, y porque el resultado final de todos modos tiene que volver a C#
      Incluso cuando no se usan ORM como Entity Framework o Dapper, conviene dejar la lógica de acceso a datos, incluido SQL, en un proyecto separado y abstraído
      Así no se dispersa por toda la aplicación y se puede reemplazar si alguna vez se necesita otro RDBMS
      Aunque en 20 años eso solo me pasó una vez
      Cuando un desarrollador junior usa LINQ, ponerle un profiler y un depurador ayuda a entender qué está ocurriendo por dentro
      A veces también es útil pedirle que lo escriba primero con un bucle for y lógica C# común, y luego compararlo con una implementación en LINQ, para ver las ventajas y desventajas de ambos enfoques
    • Si te gusta Haskell, quizá también te interesen otros usos de la sintaxis de consulta de LINQ, como la composición de parsers combinadores
      La sintaxis de consulta no está hardcodeada exclusivamente para IEnumerable; ese es solo su comportamiento predeterminado, y puede usarse casi en cualquier lado
      Funciona de forma algo parecida a la sobrecarga de operadores
      [1]: https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
    • Si la sintaxis de LINQ desapareciera mañana, no la extrañaría demasiado, pero la composición funcional es realmente potente y también más fácil de mantener
  • No entiendo por qué el equipo de dotnet no invierte más recursos y tiempo en las herramientas
    Hace falta doctest y generación de documentación, una forma de escribir pruebas unitarias mejores y más rápidas junto al código real, accesibilidad al código fuente, un entorno en el que al presionar F12 no haya que descompilar un DLL, y un hub central de paquetes y documentación como pkg.go.dev o docs.rs
    La mayoría de los paquetes de NuGet no tienen documentación alguna, o solo tienen un README de GitHub, o una wiki breve
    Otros entornos como Rust, Go, Java y Python están a años luz en este aspecto

    • Me dan ganas de bromear con que Microsoft invirtió en OpenAI porque es la única forma razonable de explorar la documentación de paquetes .NET/NuGet
      Pero, de forma horrible, también siento que eso está bastante cerca de la verdad
    • Ahora la documentación de Microsoft incluye enlaces para ir directamente al código fuente del método que estás viendo
      Ejemplo: https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
      Para el código fuente de paquetes NuGet también es fácil si se activa Source Link, pero todavía es una función relativamente nueva y no todos los paquetes la han adoptado
    • Estoy de acuerdo, pero probablemente también influye que el C# open source sea algo relativamente reciente
      Aún parece que la mayoría del código C# se escribe en empresas como código cerrado
      Si Microsoft sigue avanzando en la dirección abierta de estos últimos años, creo que mejorará con el tiempo
      Algunas de estas funciones las ofrecen herramientas como Resharper, y me pregunto si no habrá algún acuerdo explícito o implícito para no pisarse los terrenos
      Sinceramente, la mayoría de la documentación que he visto en proyectos C# era de baja calidad, así que terminaba mirando el código fuente
      Mi experiencia es que, aunque haya muchas herramientas de autocompletado, no ayudan mucho a leer, solo a escribir
    • Sandcastle Help File Builder existe desde hace muchísimo tiempo y, si mal no recuerdo, empezó como un proyecto interno de Microsoft, pero curiosamente pocas bibliotecas lo usan
      https://github.com/EWSoftware/SHFB
    • También existe una forma de escribir tests junto al código: https://clipperhouse.com/go-test-csharp/
      No estoy seguro de recomendarla
      La probé y luego la revertí; parecía que los tests tardaban más, quizá porque empeoraba el caché de artefactos de compilación
  • Más que “mejoras de rendimiento de LINQ”, sería más correcto decir “mejoras de rendimiento de su propia implementación de List
    Parece que Microsoft dedica tiempo a mejorar lo que ellos necesitan, más que a mejoras generales
    LINQ, en especial la sintaxis de consulta y no las extensiones de métodos, necesita inversión
    Principalmente hace falta reducir las asignaciones de lambdas y, si es posible, simplificar las lambdas en tiempo de compilación
    Se necesita una estrategia para que las lambdas locales de tipos por valor, o las asignaciones de lambdas, no sean una sobrecarga como lo son ahora
    Las variables de LINQ también deberían soportar comodines (_) a estas alturas, pero cuando se introdujeron en las lambdas se ignoró por completo
    Además, como último elemento de una expresión LINQ debería poder usarse un tipo elevado como IEnumerable u Option, en vez de select ...
    En ciertos casos de uso, select introduce una sobrecarga innecesaria y también limita cosas como expresiones LINQ con recursión de cola
    Las bibliotecas que, como la mía, apuestan todo por LINQ pero no usan IEnumerable, IQueryable ni extensiones LINQ siguen siendo ignoradas
    Porque Microsoft se concentra solo en mejorar el rendimiento de sus propios proyectos
    Un buen ejemplo es la inferencia de lambdas mejorada
    Se adelantó porque era necesaria para las Minimal API de ASP.NET Core
    Da la impresión de que buena parte de las funciones del lenguaje y del framework se mueven más por necesidades internas de Microsoft que por las de la comunidad
    Lo peor es que sigue creciendo el conjunto de métodos mágicos, no solo las extensiones LINQ Select, SelectMany y Where, sino también cosas como GetAwaiter
    En lugar de incorporar las características de kind de orden superior que realmente hacen falta para eliminar esta magia, Microsoft agrega funciones para ellos mismos, sobre todo para el compilador
    Por eso todo queda con tipos débiles, y el compilador apenas puede detectarlo de forma aproximada
    LINQ es uno de los diferenciadores clave entre lenguajes, pero desde C# 3 ha quedado casi abandonado
    Es realmente una lástima que todavía se vea a LINQ como algo útil solo para recorrer listas, en especial para recorrer su propia implementación de listas
    Agradezco las mejoras de rendimiento en sí, y ayudarán a muchos usuarios, pero el foco siempre es tan estrecho que limita su potencial
    [1] https://github.com/louthy/language-ext/

    • Si tienes feedback útil, convendría abrir un issue en dotnet/runtime o enviar un PR
      Muchas de las mejoras de rendimiento de LINQ tratadas en el artículo entraron de esa manera
    • La biblioteca se ve muy interesante, pero también parece estar planteada de una forma que facilita que la ignoren desde el inicio
      Hay muchas sentencias using, aunque eso no es un gran problema para quien entiende cómo dividir un proyecto en unidades necesarias y separar responsabilidades
      Pero la mayoría de los desarrolladores no estructuran así sus proyectos, y detalles pequeños como este pueden convertirse en una barrera para el desarrollador promedio
      Los desarrolladores junior muchas veces ya tienen dificultades con la sintaxis y los métodos estándar de LINQ, especialmente también con el rendimiento
      Está bien que el README mencione esto
      Normalmente las bibliotecas están ocupadas en “venderse”, así que me gusta que realmente indique dónde es fuerte y cuál es su propósito
      La mención de que no es idiomática también puede ser un problema para quienes están aprendiendo C#/.NET
      Microsoft probablemente quiera que las herramientas y el lenguaje sigan ciertas prácticas, y los nombres alineados de forma natural con la programación funcional podrían verse como un obstáculo bastante grande cuando Microsoft evalúe mejoras
      Le puse estrella al repositorio y me interesa mucho lo que construiste
      En algunas aplicaciones grandes que hice recientemente usé un tipo Result que parece más o menos similar a Option
      Pero al volver a mirar la biblioteca, sentí que, aunque pensaba que sabía bastante de programación funcional, en realidad no era así
      Soy bastante bueno en C# y he hecho cosas complejas, pero la programación funcional sigue siendo difícil de dominar aunque lea mucho; F# For Fun And Profit fue lo que mejor pude entender
      En definitiva, no significa que estés haciendo algo mal
      Microsoft apuntará al desarrollador promedio o principiante que constituye la mayor parte de su ecosistema
      Ojalá esta biblioteca pueda obtener mejoras internas
      Se nota que le dedicaste muchísimo tiempo, y con solo ver la cantidad de estrellas en GitHub ya hay señales suficientes de que hay gente que realmente la usa y recibe ayuda de ella
      Si sonó como un comentario despectivo, lo siento, pero el trabajo que hiciste es interesante y parece tener documentación suficiente como para ir aprendiendo poco a poco conceptos que no conozco
  • Cuanto más tome C# de F#, mejor
    Estoy esperando que las uniones discriminadas por fin lleguen a C# para poder hacer modelado de dominio como corresponde

    • Me parece interesante que esto se mencione con frecuencia en la comunidad .NET
      Cada vez que pasa, me surge la pregunta: “¿por qué no simplemente usar F#?”
      C# lleva años tratando de ponerse al día
      Si F# ha impulsado muchas innovaciones del ecosistema .NET y además estuvo años adelantado en funciones, me pregunto por qué no recompensan ese esfuerzo usándolo
      Si hay una dirección de desarrollo del lenguaje que quieren, deberían incentivarla con elecciones reales
      En el ecosistema Java eso funcionó de esa manera, y ahora Java también está mejorando
      Si el mercado crece, puede generarse un ciclo de retroalimentación positiva en el que también aumente el esfuerzo de ingeniería
      Al leer foros durante años, da una fuerte impresión de que el bando de C# quiere “simplemente” quedarse en su lado y esperar
      Se ve un poco tribalista, como si el equipo fuera “C#”
      No he visto mucho esta cultura en otros ecosistemas de lenguajes, y me da la impresión de que si F# hubiera estado en otro ecosistema que no fuera .NET, quizá habría prosperado hace mucho
    • También me encantaría tener tipos de unidades de medida
      Harían mucho más fácil mantener código de ingeniería o científico
    • Con OneOf[0] y Dunet[1] ya se pueden introducir uniones discriminadas con bastante facilidad
      Ejemplo de uso real: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
      [0] https://github.com/mcintyre321/OneOf
      [1] https://github.com/domn1995/dunet
    • Es un secreto a voces que F# es el campo de pruebas para funciones de C# y VB.NET, y figuras oficiales como Hanselman lo han citado varias veces
  • Lo que más extraño cuando trabajo en otros lenguajes o ecosistemas es LINQ.
    Es realmente genial que una biblioteca estándar tenga una funcionalidad así, y está diseñado de forma elegante dentro de las restricciones dadas.

  • Hay una sección relacionada en el artículo anual, del tamaño de un libro, que cubre todas las mejoras de rendimiento de .NET 9.
    https://devblogs.microsoft.com/dotnet/performance-improvemen...
    Curiosamente, HN no permitió volver a enviarlo, así que el artículo no llegó a la portada y quedó enterrado.

  • Una vez que te acostumbras a LINQ y normalmente trabajas en dominios donde LINQ brilla, ya no quieres volver a hacerlo de otra manera.

    • Amigos, no se vuelvan adictos a LINQ.
      LINQ los atrapará y terminarán resentidos con los entornos que no lo tienen.
    • Aun así, es menos potente que algo como polars.
  • Me pregunto si habrá algún libro o tutorial integral bueno para aprender desarrollo web de punta a punta con dotnet.
    La mayoría de lo que encontré era demasiado básico, viejo o de baja calidad.

    • Lo nuevo y de moda últimamente en desarrollo web con .NET es Blazor, pero fuera de la esfera de blogs de Microsoft no es muy popular, y no creo que vaya a serlo.
      Personalmente, creo que seguirá el mismo camino que Silverlight.
      Las tecnologías antiguas siguen estando en .NET 9, funcionan y se mantienen.
      Hoy en día, hacer desarrollo web con .NET por lo general significa crear APIs HTTP/JSON/REST y conectarlas con el framework de frontend que prefieras.
      En mi caso uso React o NextJS.
      Buenos términos de búsqueda son ASP.NET WebApi o, de forma más moderna, ASP.NET Minimal API.
      También se puede seguir haciendo renderizado del lado del servidor con .NET MVC usando Razor.
      Como es el lenguaje de marcado de ASP.NET MVC, puedes buscar “ASP.NET MVC Razor”.
    • Hace poco me empezó a interesar el desarrollo web con C#.
      Para bien o para mal, en .NET la forma de crear aplicaciones web parece ser, en la práctica, ASP.NET.
      Aunque la falta de alternativas resulta un poco sospechosa.
      Escuché un podcast con Andrew Lock, autor de “ASP.NET Core in Action”, y parecía alguien que domina el tema.
      Todavía no leí el libro, pero podría ser el que estás buscando.
      1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
      2: https://www.manning.com/books/asp-net-core-in-action-third-e...
    • Es un poco de nicho, pero la combinación de F# y Fable es muy potente.
      En el servidor puedes correr Giraffe sobre ASP.NET, que es una capa de programación funcional con un rendimiento similar al de C#.
      En el frontend puedes escribir React con un lenguaje de programación funcional de verdad.
      Por supuesto, también puedes compartir código F# entre el frontend y el backend.
    • Aprendí haciendo, pero dejo algunos recursos que pueden servir de referencia.
      Como libros están “C# 12 and .NET 8 - Modern Cross-Platform Development Fundamentals - Eighth Edition: Start building websites and services with ASP.NET Core 8, Blazor, and EF Core 8”, de Mark J Price, y “Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs”, de Xiaodi Yan.
      Para tutoriales, están las series de IAmTimCorey y Shawn Wildermuth en YouTube.
    • Si quieres UI con renderizado en servidor, busca materiales que usen Razor, y al principio conviene evitar los de Blazor.
      Si buscas combinar un backend .NET con un frontend JS, busca materiales que usen Minimal API.
      MVC también está bien, pero carga con mucho lastre de compatibilidad hacia atrás, y por eso surgió Minimal API.
  • Tiene que haber una forma mejor que este montón de fideos de anotaciones.
    Cada vez que veo código .NET moderno me duelen los ojos.

    • Esos atributos corresponden a la biblioteca de benchmarking usada en el artículo.
      El código de pruebas unitarias y benchmarking suele verse algo espagueti.
      Aun así, no aprobaría un PR que se viera así en lógica de negocio real.
      Si de verdad los odias, también puedes usar cosas como AspNetCore sin tocar ni un solo atributo.
    • No sé qué código .NET estás viendo.
      Yo casi no uso atributos.
  • La parte sobre que se pueden hacer más optimizaciones cuando la cadena termina con métodos como Count(), First(), Last(), ElementAt(), Sum(), y que, por ejemplo, OrderBy(criteria).First() puede optimizarse para ejecutarse como Min(criteria), podría ser útil.
    Pero lo correcto sería escribir mejor código desde el principio.
    Sería interesante para cadenas generadas dinámicamente, pero si haces estas operaciones en código escrito a mano, se siente como una especie de refuerzo positivo torcido.
    La biblioteca estaría reconociendo patrones ineficientes y corrigiéndolos.
    Como mínimo, espero que haya feedback que sugiera mejorar el código subyacente.