Decir mala palabra para LINQ

Durante toda mi corta vida como programador de C #, pensé que LINQ no se trata principalmente del rendimiento del código, sino del rendimiento del programador, qué tan rápido escribe el código y no qué tan rápido el procesador ejecuta este código. Y los problemas de rendimiento tanto del código como del programador se resuelven después de identificar los cuellos de botella. Por lo tanto, a menudo escribo var groupedByDate = lst.GroupBy(item => item.IssueTime.Date).Select(…).ToList()y hago esto no por daño o intención maliciosa, pero es más fácil depurar el código. Para la lista, simplemente coloque el cursor del mouse sobre el texto de la variable e inmediatamente puede ver si hay algo allí o no.


comienzo


Después de leer el artículo "Artículo infructuoso sobre acelerar la reflexión " y calmar las emociones acerca de "alguien está equivocado en Internet", me preguntaba si es posible hacer que el código "LINQ to Objects" se acerque a "manual". ¿Por qué exactamente LINQ to Objects? En mi trabajo, a menudo uso solo los proveedores de LINQ to Objects y LINQ to Entities. El desempeño de LINQ to Entities no es crítico para mí; es fundamental qué tan óptima será la consulta SQL generada para el servidor de base de datos de destino y qué tan rápido la ejecutará el servidor.


Como base, decidí usar el proyecto del autor del artículo. En contraste con los ejemplos de Internet, donde un código se compara principalmente como integerList.Where(item => item > 5).Sum()un código "manual" que contiene foreach, ify así sucesivamente, el ejemplo del artículo me pareció interesante y vital.


Lo primero que hice fue usar una sola función de comparación de cadenas. En el código fuente, en los métodos que realizan la misma función, pero ubicados en las esquinas opuestas del anillo, se utilizan diferentes métodos, en un caso variable0.Equals(constant0, StringComparison.InvariantCultureIgnoreCase)y en otro variable0.ToUpperInvariant() ==constant0.ToUpperInvariant(). Es especialmente molesto para la constante que cada llamada al método se convierta a mayúsculas. Elegí la tercera opción usando en ambos casos variable0.ToUpperInvariant() ==constant0InUpperCaseInvariant.


Luego, se descartó todo el código que no estaba directamente relacionado con la comparación del rendimiento de LINQ y el código manual. El primero en ser atrapado fue el código que crea e inicializa el objeto de clase Mock<HabraDbContext>. ¿Cuál es el punto de crear una conexión de base de datos para cada prueba, una vez es suficiente? Se ha movido más allá de las pruebas de rendimiento.


IStorage _db;

[GlobalSetup]
public void Setup()
{
    _db = MockHelper.InstanceDb();
}

private IStorage DBInstance => _db;

… — , ! Linq «» !


imagen


« - » . LINQ . , — .



, , LINQ vs «» . . . .


, , , ([Params(1, 10, 100, 1000)]). . . . StatisticColumnRelStdDev.


FastHydrationLinq, ManualHydrationLinq — . , , (Fast)(Manual)(Slow)HydrationLinq vs (Fast)(Manual)(Slow)Hydration, ManualHydrationLinq. FastHydrationLinq - . .


ToArray, ToDictionary IEnumerable<T> . , FastContactHydrator2. - Action<Contact, string> c ConcurrentDictionary<string, Action<Contact, string>> IEnumerable<KeyValuePair<string, Action<Contact, string>>>. GetContact2, Sum, .


protected override Contact GetContact2(IEnumerable<PropertyToValueCorrelation> correlations)
{
    var contact = new Contact();
    int dummySum = _propertySettersArray.Join(correlations, propItem => propItem.Key, corrItem => corrItem.PropertyName, (prop, corr) => { prop.Value(contact, corr.Value); return 1; }).Sum();
    return contact;
}

ParseWithLinq


public static IEnumerable<KeyValuePair<string, string>> ParseWithLinq2(string rawData, string keyValueDelimiter = ":", string pairDelimiter = ";")
    => rawData.Split(pairDelimiter)
    .Select(x => x.Split(keyValueDelimiter, StringSplitOptions.RemoveEmptyEntries))
    .Select(x => x.Length == 2 ? new KeyValuePair<string, string>(x[0].Trim(), x[1].Trim())
                                : new KeyValuePair<string, string>(_unrecognizedKey, x[0].Trim()));

.


FastContactHydrator2, , , , -, ( ParseWithLinq ParseWithoutLinq). . , , ToArray. 10 var result = new List<PropertyToValueCorrelation>(10). 10? 42 , 10 . Fair .


. . GetFakeData . , .
, , « » (RelStdDev). N=1000.


:


-  `ManualHydration`, `SlowHydration`, `FastHydration`, `ManualHydrationLinq`, `SlowHydrationLinq`, `FastHydrationLinq` -     ,  ;

  • ManualHydrationFair, ManualHydrationFairLinq, FastHydrationFairLinq — , ;
  • FastHydrationLinq2 — , , - , LINQ;
  • N , ;
  • Ratio FastHydrationLinq2.

imagen
Linq-


? — FastHydrationLinq2 ManualHydrationLinq Ratio. 26,361.37 μs 26,422.17 μs N=1000. / N. ManualHydrationFairLinq, 8% , . FastHydrationFairLinq, 1% -.


Fair-. , ManualHydrationFairLinq 8% . FastHydrationFairLinq FastHydrationLinq 12%. Linq .


, . . , MockHelper.InstanceDb() Setup, . — , GetFakeData, . , — . MockHelper.InstanceDb() .


N, MakeGarbage. False , True — .


imagen


N

image


Voilà, , . — N=1. 10% 82% .


… .



, , «» — Linq-, :


imagen
N=1 MakeGarbage=True «»


, , , LINQ .
«Gen X» «Allocated» N=1 MakeGarbage=True o , FastHydrationLinq2


|                                |      Gen 0 |   Gen 1 | Gen 2 |   Allocated |
|«»     |    20.5078 |  0.4883 |     - |    63.95 KB |
|Linq-        |    20.7520 |       - |     - |    63.69 KB |

ManualHydrationFairLinq .


. , , — .
, ? . , N [Params(1, 100)] MakeGargabe=True. «» — Linq-, . — , — Linq-, Linq — — — Linq. — . .


, , — Linq- . , . paint.net, . paint.net — paint.net . paint.net — «» , — Linq- .


N 1, 10, 100, 1000 MakeGarbage [ParamsAllValues]. paint.net, — «» . — Linq- . paint.net Visual Studio — «» . , 80- . , Linq- 2%.



Después de escribir las pruebas y analizar los resultados, mi opinión sobre LINQ no ha cambiado: al usarla, mi productividad en la escritura de códigos es mayor que sin ella; El rendimiento de LINQ to Objects está bien. Usar la ejecución retrasada del código LINQ como un medio para mejorar el rendimiento no tiene mucho sentido.


Si en un proyecto hay problemas de rendimiento debido al recolector de basura, la lesión de nacimiento .net, probablemente elegir esta tecnología no fue la mejor solución.


Mi código de prueba está disponible aquí .


All Articles