C# LINQ Performance Tips #1 - Let keyword & Custom Lookup

แชร์
ฝัง
  • เผยแพร่เมื่อ 28 พ.ย. 2024

ความคิดเห็น • 59

  • @johnfrian
    @johnfrian 4 ปีที่แล้ว +3

    Honestly, the best LINQ performance tip is to not use LINQ. In many cases it's a compromise between readability vs performance, choosing between LINQ and a custom loop that does exactly what you need. There are, of course, cases where LINQ provides nice additions (e.g. null-checks) without doing anything too crazy on top of that (like .Any()). Just be mindful when using methods like GroupBy() which can be up to 9 times slower than a simple foreach.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      Watch the other videos :) you can have your cake an eat it too. It requires some work but you can get LINQ at zero cost, but not out of the box you have to improve and re-implement methods and operators.

  • @edwardevlogiev4087
    @edwardevlogiev4087 4 ปีที่แล้ว +2

    Hey, thumbs up for all the effort.
    I would like to point out that LINQ is designed for readability, not performance, and it's excellent in situations where you need to apply complex transformations over not very big collections.
    Optimizing where optimizations are not need is, in the best case, harmless.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +1

      True, although one can have it's cake and eaten with LINQ by improving some of the operators by using structs, value delegates, de virtualisation, better branching code etc.
      My next couple of videos in the series show how to achieve this with LINQ, plus there are frameworks that do this out of the box as well:
      ♦ github.com/NetFabric/NetFabric.Hyperlinq
      ♦ github.com/reegeek/StructLinq

    • @edwardevlogiev4087
      @edwardevlogiev4087 4 ปีที่แล้ว +1

      @@LevelUppp Yup, certainly and there's nothing wrong with that. My argument is mostly aimed at beginners when they fall into the trap of feeling they MUST optimize or they are doing it wrong.

  • @David-id6jw
    @David-id6jw 4 ปีที่แล้ว +1

    Filter_ToLower_04 has another difference from the first three: It doesn't return the result. So it doesn't have to copy the reference back to/off the stack, which means less memory access, etc. I'm pretty sure that's why it's faster than version 03, and not because of the change in comparison order.
    Though you're still correct that the number of comparisons in the respective lists will have an impact on a more elaborate app.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      While pushing and pooping the stack does have an impact, it's incredibly fast, in this specific case it was the branch order since the data is skewed in such a way that the branch order does make an impact.
      That missing return was an accidental omission on my part and I've fixed it later.

  • @therealxunil2
    @therealxunil2 4 ปีที่แล้ว +3

    I would emphasize the point that if you're examining millions of records, those 5ms in your test case will add up to a significant savings in time.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      The problem with that is that if you're around ~5% better it would be good to do a statisticalsignificance test to be sure that your gains are not just random noise.

  • @JacekGasiorowski
    @JacekGasiorowski 4 ปีที่แล้ว +11

    Hello, I might be wrong but your example with GroupBy_O1 might be totally incorrect.
    By using your group by LINQ query you only building expression tree which is not materialised anywhere (you are not calling .ToList() after query or just .Count()) so your mesurement only shows how long it takes to build the LINQ expression for the runtime. I is never executed. I tried that example myself and with .ToList() call it takes almost 9 times more time than withou it. I think it might be a mistake on your side because you used .ToList() call in examples with filtering

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +2

      You're right, thanks!

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +1

      I'll correct that in another video

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +2

      And here I was thinking that there's a saving grace for LINQ but no :( although it's sill not that bad in performance numbers but my Lookup absolutely destroys LINQ.

    • @JacekGasiorowski
      @JacekGasiorowski 4 ปีที่แล้ว

      @@LevelUppp Exactly :) good job :)

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      @@JacekGasiorowski No that makes me sad since I wanted to love the built in Lookup ;-(

  • @danflemming3553
    @danflemming3553 4 ปีที่แล้ว +8

    At 11:50 where you talk about LINQ's Last() method, you say that LINQ doesn't check to see if the enumerable is actually a list.
    That's incorrect. LINQ does that actually:
    github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L1091
    And in the next line, you can see it actually calls the index:
    if (count > 0) return list[count - 1];
    github.com/microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L1093
    More importantly, your code works faster also because you don't check like LINQ to see if the list has any elements in it before accessing the index. This isn't the main cause why your code is faster, but it's important to note.
    The performance penalties you're seeing are of different reasons, not because LINQ doesn't check if the instance is a list

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +1

      Yeah the old version of LINQ didn't do that check. The new version indeed does the check.
      The type check is what's slowing it down, and since it's using the interface version all calls are considered VCalls and it's another tiny penalty that is relevant at scale.
      Thanks ❤️

    • @danflemming3553
      @danflemming3553 4 ปีที่แล้ว +2

      Don't get me wrong, your content is OK, LINQ is slower but some of the reasons you say why LINQ is slower not all of them are correct. Keep up the good work and thanks

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +2

      @@danflemming3553 Hey, I'm not bothered or anything like that. I think you're doing great work correcting all of the mistakes that I've made. This will lead to better quality videos, more discussion, small arguments, corrections and everyone will benefit at the end. Thanks :)

    • @danflemming3553
      @danflemming3553 4 ปีที่แล้ว +2

      @@LevelUppp Wow, great attitude dude 👍🏻 Many don't understand that and would be offended! It's exactly my point, the idea is to improve, we all need to learn and improve. I have a lot of things I need to improve myself and your videos are great!

    • @danflemming3553
      @danflemming3553 4 ปีที่แล้ว

      @@LevelUppp Actually Last() works like that at least since 2014, that's at least 6 years ago 🙂:
      github.com/microsoft/referencesource/blob/9da503f9ef21e8d1f2905c78d4e3e5cbb3d6f85a/System.Core/System/Linq/Enumerable.cs#L967

  • @nvtmjfan
    @nvtmjfan 2 ปีที่แล้ว

    What is diff between method 3 and 4.

  • @Bjarkediedrage
    @Bjarkediedrage 4 ปีที่แล้ว +1

    When trying to do ECS in c# some time ago, I hit a wall when trying to provide a nice way of enumerating quickly throught various data buckets in an easy way. Mainly because C# can't inline lambdas. (Yet??).
    For instance:
    void IterateOnDataBuckets_Slow(Action iterator)
    {
    for (...) { // Imagine a more complex iterator.
    iterator(someData[i]);
    }
    }
    interface DataBucketIterator {
    void Evaluate(ref Data data);
    }
    void IterateOnDataBuckets_SillSlow(DataBucketIterator iterator)
    {
    for (...) { // Imagine a more complex iterator.
    iterator.Evaluate(ref someData[i]); // still quite slow.
    }
    }
    void IterateOnDataBuckets_FastAndInlineable(T iterator) where T : DataBucketIterator
    {
    for (...) { // Imagine a more complex iterator.
    iterator.Evaluate(someData[i]); // Since it's know what T is, it's able to inline this function.
    }
    }
    But of course, having to write a type just to enumearte the data, so I stopped the ECS experiement there. Wondering if you have any experience with that sort of problem?

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      Lambdas that need to capture context usually cannot be inlined since the compiler will create a class and push all of the captured variables there, thus inlining is out of the question. This is a current limitation of the compiler since in a lot of cases such treatment shouldn't be needed.
      Then there's a general problem that delegates cannot be inlined at the calle site yet another limitation that could be improved.
      What I would do is a struct that describes the iterator this can be created with an lambda function but it returns a struct that describes what needs to be done, this would let you solve the problem, the matching engine would be a bit more complicated (and perhaps limited) but you would see performance gains.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      If you need to iterate quickly you should pay the most attention to the data layout of your components, inlining while help should not be the primary cost. Are you using SOA for components? Have you tried doing SOA with SIMD? For well crafted components and systems using the SOA over SIMD is usually very fast.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      Sorry one last question :) What C# compiler are you using? If it's the Unity compiler things are a bit different there, and if it's the Burst Compiler thins are even more extreme and with the C#9 on the horizon you could try using function pointers but the Unity toolchain is even faster when using SIMD.

    • @Bjarkediedrage
      @Bjarkediedrage 4 ปีที่แล้ว

      @@LevelUppp It was mostly an experiment to see how close I could get to c/c++ performance. I've not tried and use SIMD before really, but I've been wanting to since seeing your videos.

    • @Bjarkediedrage
      @Bjarkediedrage 4 ปีที่แล้ว

      @@LevelUppp Yeah, it's really cool how fast they've gotten things with burst. They also inline the delegates, (but I have had some bugs with scope and capturing variables^^). Okay, I was not aware of C# 9 function pointers, that is awesome!

  • @atmanirbharbharat
    @atmanirbharbharat 2 ปีที่แล้ว

    i there any wy to replacement of .contains. for large DB performance issue

  • @abdelrhmanahmed1378
    @abdelrhmanahmed1378 2 ปีที่แล้ว

    in groupByLookup you dont deal with collisions,and when i see the imp of groupBy provided by Microsoft it look very similar to yours , so why the performance gap is hug ?!

  • @psychotrout
    @psychotrout 4 ปีที่แล้ว +2

    LINQ query syntax is very ugly to read, the method chain version is much more readable and modern in my opinion:
    transactions.Where(x => ...).Selec(x => x...);

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      Yeah I agree.

  • @frankroos1167
    @frankroos1167 3 ปีที่แล้ว

    For the ToLower bit: I am missing the option of first uniting the collections to be filtered by into a single one. That should also limit the number of ToLower calls to 1.

  • @theincredibleillmo9385
    @theincredibleillmo9385 4 ปีที่แล้ว +6

    Great tips, just be aware about coughing all the time, it is really annoying when someone uses headphones.

    • @Cerstani
      @Cerstani 3 ปีที่แล้ว

      If you thought that was annoying, you should NEVER subject yourself to a DarkSydePhil (DSPGaming) video..

  • @lemonade2345-j4e
    @lemonade2345-j4e 6 หลายเดือนก่อน

    Great video. I would only recommend to work on a proper naming convention, as you named all of the tested methods with quite generic (nothing saying) names.

  • @ProfessorCodemunkie
    @ProfessorCodemunkie 4 ปีที่แล้ว

    With the let example I wonder what the performance would be like using LINQ method syntax i.e. transactions.Where(t => { var nameLowerCase = t.Name.ToLower(); if.... }).Select(...

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      Not sure we can check :) but I assume it would be faster?

  • @sanjayi6245
    @sanjayi6245 4 ปีที่แล้ว +3

    Very good video.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +1

      Thanks! :)

    • @sanjayi6245
      @sanjayi6245 4 ปีที่แล้ว

      @@LevelUppp Can you tell me what font you are using in Visual Studio.

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      @@sanjayi6245 It's consolas, I've got it by default.

    • @sanjayi6245
      @sanjayi6245 4 ปีที่แล้ว +1

      @@LevelUppp Ah. Okay. Anyway continue what you are doing. Cheers

  • @omarbousbia6916
    @omarbousbia6916 4 ปีที่แล้ว +1

    Great tips,,, thank you

  • @m057d0p3
    @m057d0p3 4 ปีที่แล้ว

    Why dont you use BenchmarkDotnet to measure?

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว +1

      Too slow to measure. It takes an average of ~1-5 minutes per run.
      However new videos include Benchmark.NET as
      well either by having it already pre-computed or by speeding up the video 15x times when the benchmark runs.

  • @CHITRANSHSHARMA
    @CHITRANSHSHARMA 4 ปีที่แล้ว

    I am 639th subscriber

  • @jfaquinojr
    @jfaquinojr 4 ปีที่แล้ว +2

    Anybody else cringe when he coughs?
    Hope OP is ok

    • @LevelUppp
      @LevelUppp  4 ปีที่แล้ว

      Had a Flu when I was recording this, sorry :)