Say poor word for LINQ

All my short life as a c # programmer, I thought that LINQ is not primarily about the performance of the code, but about the performance of the programmer, how fast he writes the code, and not how fast the processor executes this code. And the performance problems of both the code and the programmer are resolved after identifying the bottlenecks. Therefore, I often write var groupedByDate = lst.GroupBy(item => item.IssueTime.Date).Select(…).ToList()and do this not because of harm or malicious intent, but it’s just easier to debug the code. For the list, just place the mouse cursor over the text of the variable and you can immediately see if there is something there or not.


Start


After reading the article “ Unsuccessful article about accelerating reflection ” and calming down emotions about “someone is wrong on the Internet”, I wondered if it is possible to make “LINQ to Objects” code close in performance to “manual”. Why exactly LINQ to Objects? In my work, I often use only the LINQ to Objects and LINQ to Entities providers. The performance of LINQ to Entities is not critical for me; it is critical how optimal the generated SQL query will be for the target database server and how quickly the server will execute it.


As a basis, I decided to use the project of the author of the article. In contrast to the examples from the Internet, where a code is mainly compared like integerList.Where(item => item > 5).Sum()a “manual” code containing foreach, ifand so on, the example from the article seemed interesting and vital to me.


The first thing I did was use a single string comparison function. In the source code, in methods that perform the same function, but located in opposite corners of the ring, different methods are used, in one case variable0.Equals(constant0, StringComparison.InvariantCultureIgnoreCase)and in another variable0.ToUpperInvariant() ==constant0.ToUpperInvariant(). It is especially annoying for the constant that each method call is converted to uppercase. I chose the third option using in both cases variable0.ToUpperInvariant() ==constant0InUpperCaseInvariant.


Then all the code that was not directly related to comparing LINQ performance and manual code was thrown out. The first to be caught was the code that creates and initializes the class object Mock<HabraDbContext>. What is the point of creating a database connection for each test, once is enough? It has been moved beyond performance tests.


IStorage _db;

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

private IStorage DBInstance => _db;

… — , ! Linq «» !


image


« - » . 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.

image
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 — .


image


N

image


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


… .



, , «» — Linq-, :


image
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%.



After writing the tests and analyzing the results, my opinion about LINQ has not changed - using it my productivity in writing code is higher than without it; LINQ to Objects performance is fine. Using delayed execution of LINQ code as a means of improving performance does not make much sense.


If in a project there are performance problems due to the garbage collector - the .net birth injury, then probably choosing this technology was not the best solution.


My test code is available here .


All Articles