C# LINQ Performance Tips #3 - Where & Heap Allocations

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi everyone so today we're going to be talking about link performance tips this is a video free out of who knows how many so today we're going to be talking about a single keyword or operation uh whatever you want to call it it's called where and at the same time we're gonna be discussing interfaces unboxing obviously accidental boxing is bad and additional boxing depends probably it's good so um in my previous video we discussed um why you know losing type information in link is bad and needs to performance degradation and we discussed that where um iterator so um this time around we're going to look at the wear operation and you know like close detail and what we're gonna do is um we're gonna try and implement a better version of this operator so as it turns out and i as i told you this will box we don't have any concrete type information here so we just know that we have i i a numerable um same here so what's going to happen here is when we call the wearer on innumerable we're gonna act we're gonna allocate stuff on the heap and it will not box because it's a struct that's you know gonna be basically promoted to a class and box it's because uh this guy create you know the where operation creates a class anyway so in that sense it will not box but what it will box is it will box your enumerate or because if you have a numerator and it's you know a struct then it's gonna box in a way that's trucked if it's a class then it's just a class right so that's bad for a couple of reasons first of all it's bad for performance and other like uh link operations like first default uh like single and probably some others don't do that they just take your enumerable take your enumerator and just go just like roll with it right so this is different because it creates a wrapper class around our enumerator in a way so why is that even done in such a way right so let's look at the where operation so we have a bunch of null checks here and what we have here as well is if this is a list and we're going to be just talking about the list now we're going to create this class where list iterator so let's see uh you know how it's implemented so like i said it's an iterator that will capture our enumerator so if it's a struct then already it's gonna it's gonna be promoted to the heap uh it will capture our predicate and our source and it will use that to filter out anything that we want filtered because we pass the predicate and you might still be wondering why it's sort of done this way and we have to in order to like understand that we have to look at the iterator class so when we go to the iterator class we're gonna see that this class has you know a thread id so it captures thread context in a way so it's thread aware it also has a dispose operation which is sort of like you know implemented in a simple way but all of these iterators that use and inherit this from this class will um implement their own sometimes much rather they're gonna implement their own custom dispose so this is used because uh we can work with multi you know we can use this iterator using a multiple threads so things like you know probably parallel link or stuff like that could use that we can do you know create our own bunch of threads or tasks and you know share that iterator as well and if we have stuff that needs disposing then we can dispose of these resources and every time you have a scenario like that then you just cannot use the stack because the stack is tied to single thread so there's no way to be able to use that so i'm just you know my wild guess is that this is why it has been created like that because we can share this class between threads and that's you know in certain situations that's good okay so moving on let's move to a specific code example let's use the where operation so i have a function here that we're gonna test in the loop um i have a list of 15 elements and what we're going to do here is we're going to call where where x is greater than some like number and that number is nine so we're going to capture everything after nine and then we're gonna do a tool list and return the result obviously so let's see how the link version performs ninety-ish six milliseconds so um yeah again um testing procedure is not benchmark.net because that requires a lot of time it's simplified version but what we're trying to do is x uh amount of time faster or slower analysis and sometimes orders of magnitude analysis so um small performance differences doesn't don't really matter but uh since you asked about that in the comments i'm gonna add my results from benchmark.net at the end as well all right so this takes 90-ish milliseconds all right so what we can do here is we're we're gonna test how much faster uh a non-linked version of this code would be so we're just gonna have a list we're gonna do a for loop and we're just gonna return that list obviously this is this is not the same thing because link is lazy you can do stuff with this iterator uh you you can filter it out even more and more and more using you know different operations this is not that but let's see how it performs so this takes 46.5 milliseconds something like that all right so uh let's test another version version which is not lazy but i think it's interesting so the list has a method called find all and we can use that method to filter out items that we don't uh you know that we don't want or much more that that items that meet the criteria we're gonna be generated and like you know we're gonna return a second list so let's test the performance of this guy 66 milliseconds so that's pretty good as well obviously faster than link there is no surprise but we would like to use link because we like the laziness we like a bunch of things associated with link we like the expressiveness we like that abstraction so let's try and you know try let's try to speed up the link version so um one thing that people do and this is a mistake obviously uh is that they implement some sort of like custom um operation uh they override the where and what you're gonna find is that they're going to do this so they're going to create a new list filter out stuff by predicate and return the list and then they're going to do a tool list so it's this is obviously wrong but it's just to show you that sometimes mistakes like that can happen and that's obviously not good because uh what we're doing is we're doing a looped version um that kind of works like link but not really and we're doing two list lists so that's not good um i don't think there's a reason even to test the performance of this because obviously this is not what we want and not surprisingly this is the slowest version of them all so let's look at something more sensible uh again we're gonna implement our where operation but this time around we're gonna create a custom enumerable and um a bunch of you know i got a bunch of comments that um i don't do these tests uh correctly because i skip on the null checks again this is orders of magnitude analysis but um i think this is a valid point so i'm going to do new checks from from now on so if a link version has a new check my version is going to have it will check it might not be the same check or the same if statement but it's going to be a check anyway so we're going to create this this cloud this truck much rather so this truck um what it is it's a read only struck that that captures our predicate and ours list and what we're going to do here is we're going to have a custom signature of get enumerator which we're gonna inline and for completion we're gonna have the interface versions as well of good enumerator and we're gonna have obviously a struct called enumerator where we're gonna use our predicate when we move next to filter items out so um you know we have a struct so it would be good to not box this truck by accident and let's test the performance so the performance of this guy is really really slow so that's not what we wanted to end up with really and the performance is slow because we're still going to box and where we're going to box is first of all we're going to return the e n variable and um that probably is going to box already but then we're going to call a to list and to list expects a and a i enumerable as well so still we're gonna box and just to prove that we're in the box let's you know fire up the debugger and see that we're gonna box let's compile uh let's move to the debugger let's run our code sample let's break we don't need to break and yeah we have our custom enumerable here so that's not what we wanted really as you can see here it's ours because it's our custom class and we managed to capture everything in that class so our predicate our source and our enumerator as well unfortunately so that's no good and why is that happening well like i said if you're going to use interfaces for arguments or for return types um there's a huge chance you're gonna box there's a chance that you're not gonna box because um c sharp or smart.net has uh something that uh you might probably know it's called interface d virtualization and in very simple cases it can actually you know detect that you have a single implementation of the interface and it will say then well okay so if just one class is implementing that interface i'm going to de-virtualize it and treat like a class construct and then that would be good but not in this case so in this case we're in the box so we have to do something to you know combat this problem so what we can do here is we can use our enumerable but return a concrete implementation but like i said that's not going to help because the to list expects i enrollable still so what we have to do is we have to implement our custom tool list unfortunately but when we do that then we're going to create a new list here we're going to do a for each loop which will not box and we're going to add a bunch of stuff to the interface and the reason why it's not going to box because we're not going to use the interface version here because remember we have this signature and this signature we're going to be used so that's good all right so to um verify that it's true let's see how it actually performs and then maybe let's run a debugger so that's really really good that's 63 milliseconds it's probably faster than the list find version and let's comment this out for performance reasons um yeah so let's compile and let's run our debugger yet again and hopefully we're now gonna see a version that doesn't have anything associated in the heap which is our custom enumerator or enumerable no nothing okay so yeah that's good this is what we wanted so let's detach and yeah as you can see hopefully you can see that this version is really really fast and since i promised uh you know performance charts of benchmark.net so this is the the performance of benchmark.net the test was real simple we just have had a bunch of methods where we called a where a tool list and returned that list each and every time so that's why we're not seeing milliseconds but nanoseconds and the performance characteristics here so the mean is you know in nanoseconds but as you can see here uh the link version was the slowest one if we disregard this mistake here that we made uh well actually the customer label is really slow as well but this is the link version this is the where loop obviously it's going to be the fastest one then we have the find all which i think we should be faster but for some reason in this this test case it was just that you know like a couple of percent faster actually what 30 30 percent faster which is pretty good but still i'd expect better and this is our custom link and um enumerable with custom tools and custom wear operator so this is really cool because it's faster than find all and it's really close to the wear loop you know case so hopefully you can see that it's not all bad we can implement faster methods than uh just you know link provides we have to be very careful when doing that but it's a possibility like i said before there are frameworks to that do this exact thing uh but you know it would be good to know how to do them yourself because maybe you don't need a framework maybe you just need a small functionality so that's it for now um if you liked the video please leave a like subscribe if you like you know this video and some others and if you found some mistakes uh something you know that i have missed um please leave a comment i try to respond to every single comment currently um because i'm i'm not you know there's not enough comments yet so i'm i'm trying to respond to every single one and i'm gonna read every one so yeah thank you for watching and you know see you in the next video bye [Music] you
Info
Channel: LevelUp
Views: 1,451
Rating: undefined out of 5
Keywords: C#, dotnet, dotnetcore, performance, linq, memory, allocations, csharp, lecture, programming, tips, tutorial
Id: wUY-UdkI6GQ
Channel Id: undefined
Length: 17min 6sec (1026 seconds)
Published: Mon Aug 17 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.