The weirdest way to loop in C# is also the fastest

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everybody I'm Nick and this video I'm going to show you what is the weirdest but also surprisingly the fastest way to iterate a collection in C sharp without going into unsafe code territory this is a technique actually that is used by Microsoft themselves in places like the dotnet runtime and I want to bring it here to you today explain how it works and compare it with other ways of iterating collections if you lack of content and you want to see more make sure you subscribe during the summer notification Bell and for more training check out nickjobses.com but before I move on I want to let you know that I'm running a massive black Friday discount at my website nickjackson.com which is where I'm hosting my own full length courses until the 28th of November you can go there and use discount code BF 2022 or 25 of any individual cost or 15 of any of the already discounted bundles these courses contain real world techniques from experiences that I had working in Enterprise and high scale microservice systems and their packets in a very easy to consume way for all of you so if you want to do what thousands of you have done already check the link in the description and go to nickjazzles.com now let me show you what I have here and this is a bit of a follow-up video in the previous video I did about fast looping in C sharp and just refresh your memory these are the techniques we covered in that video so we had an array or a list in that video and then we checked normal for Loop for each Loop and also for looping on a span and I showed you how you can get a span of a list and obviously the one on the span was the fastest out of all of them but in this video we're gonna go a bit further the technique we're gonna see still uses spans but it actually approaches it differently making it usable in other constructs and in other scenarios now I'm gonna show you just the loop here but we're gonna see some use cases for Microsoft themselves to see how they're using it now let's start with the setup so first I'm gonna use a random class so I can get deterministic random data every time I run this example and then the easiest way you can probably iterate this as all of you know is the normal for Loop so this is what it looks like if I run it you'd expect to see a hundred numbers printed in the console if I run again it will be the same numbers printed because we're using that random class with a pinned seed now Ryder my ID actually suggests change this to a forage Loop and if I do so yes we're going to get the same numbers and the same thing but actually compared to the 2 4 which will be a bit slower we're gonna see that in some benchmarks later as well so if you really care about squeezing as much performance as possible you should use a for Loop not a forage Loop the explanation as to why this is the case is because the list actually has my numerator and if we see how this code is being translated you will see that here the for Loop isn't using any numerator it is just a normal for Loop when it's being lowered into low level C sharp however if I change this to a 4 inch Loop and I recompile so this is reconstructed you can see that now we have this enumerator here which needs to be enumerated with this move next and then it needs to be disposed in the end so it's a bit more elaborate so this is why you have this small difference in performance yes in dot Net 7 the performance difference is way smaller and we no longer actually have the extra memory allocations that for each Loops would also have so they're better now but they're still not as performant as a for Loop now let's remove all that and see what's this technique I'm talking about the first thing you need is to actually get a span out of your collection I'm gonna get a span of int here and I've talked about what the span is in a dedicated video going very in-depth on how it works so I highly recommend you check that out but if I say list as span and I use the collections Marshall dot as span method then I can actually get a span of the list items and you have to be careful this as span method actually accesses the internal items array which is backing up the list items and if you try to mutate the items while you're enumerating a list using this technique you will not get an error so you have to be careful and now you're probably thinking hey Nick I have the span why don't I just iterate the list as a span and if I actually run this I will get the exact same result and this is actually super fast it is in fact the fastest way we could run this in that previous video I showed you however we're actually gonna do something different now we're still gonna have the loop but here's how we're gonna approach it outside of the loop we're gonna get a ref VAR of the search space and we're gonna get that using ref memory Marshall dot get reference and we're gonna get a reference to that span originally Span in this case it is a span so list as span and I'm gonna use this and if we actually see the description of what this method is doing behind the scenes as it says it returns a reference to the zeroth element of the span so the first element of the span and if the span is empty it returns an empty reference let's run this let's debug this to see exactly what this will return so let's step over that and as you can see here we have the items all of them three one one one one four and if I go to the search page the search bar is the default value is 3 1 1 which is the first item in that collection which is also the first item now in our spannel which is a contagious piece of arbitrary memory which basically means we have our starting point and now what we're going to do is say VAR item equals unsafe dot add it will make sense I promise ref set space comma I and if I do console Dot rightline and say item and I run this let's see what happens as you can see same numbers all being printed now before I dive into the explanation as to why this works I'm actually going to make a record which is a reference type not a value type to show you that this also works with let's say a wrapper class around our number so int number comma string text so have both things and then I'm going to change this from being a simple thing that returns a list of integers to something on it as a number equals random dot next over here I'm gonna return it in two ways first I'm gonna say new wrapper and then have the number itself and then number dot to string which basically means that this is now the span of wrapper which means that if I run this again and just print everything as you can see everything still works we still have the same results being reliably returned now why does this work will do things first we get the reference which is the zeroth point as you can see here it actually says returns a reference to the element of the span at index zero but the reason why the unsafe.add method works is because as you can see here it adds an offset to the given managed pointer and the offset is the index of the loop and of course this can be used to get the index or to point read something in that span by just saying okay this is my starting point the reference to my search space please give me the item that is four levels deep into that span and before we go into benchmarks I want to show you how much this is used in Microsoft's own repositories as you can see in the dotnet runtime itself this unsafe.add method is used extensively to use that offset and then try to be very efficient with reading something in a span and of course the runtime has been optimized to use spans heavily so that's why we see I don't know five pages or more 121 use cases of this and I've seen many Library authors use use this as well for example Michael's type from the hot chocolate project is also using this it can be very beneficial if you're working with spans to get as much performance as possible and even though this is into the unsafe class it doesn't mean you're going to unsafe code at this point I should point out that even if you're using two array this will also work this of course means you don't have to use the as span method because you can convert an array to a span and this will still work and you also maybe don't want to have the spun conversion here because there's a get array data reference over here so you can just pass down the array itself and you can get the exact same experience nothing really changes the numbers are the same as you can see now you might be wondering okay Nick this is great but how much faster is this how does it perform to the other versions and should I even be using this in my code if I don't really know what I'm doing so I'm going to bring some benchmarks in here so for all the benchmarks I'm using benchmark.net and you can actually grab the code from the description if you want to play around with it yourself but this is the test we have so so we have the same item initialization with a hundred hundred thousand and one million items in those collections the reason why we're doing this is to see how the different techniques Scale based on the size of the collection so is it linear scaling is it exponential and so on and as you can see here we have a for Loop for each Loop a for loop on a span and then the unsafe loop on that span using the reference and the unsafe.add method now I didn't just want to run this on an array of integers because integers are value types and arrays are a bit special when it comes to how they can be optimized so I actually run this Benchmark with an integer array with an integer list with an integer wrap class over here which is a reference type and then also with an integer wrap list and here are all the results where we have an integer array and in the wrap array a list of integers and then a list of integer wraps and we're gonna go with the bottom one which is the list of the reference types effectively and as you can see here foreign forage Loop for 100 items 51 nanoseconds but then for loop on a span and the unsafe dot add for loop on a span are basically the same performance so they tie for the fastest now as you can see scaling to a hundred thousand items in that collection we go to 50 microseconds so the scaling actually looks to be linear here and I would say that these two are still within margin of error so equally as fast at this point and from what we can see in one million items this scaling Trend gets a bit looser but we still have the exact same scaling between the different types where we have almost a millisecond here while here we have almost half a millisecond or 652 microseconds now if we go to the list of integers which is the value types interestingly enough we see the same scaling but actually here it is more consistent so a for loop on a span and unsafe dot add on a span seem to be performing exactly the same as you can see over here but now the scaling stays the same all the way to 1 million so working with value type seems to be performing better than working with reference types which is what we'd expect to be honest now interestingly enough having an array of reference types over here the for Loop is actually slower than the four each loop it's actually twice as slow so there seems to be some optimization there that makes the forage Loop be faster than the for loop it's very weird but that's the number I got and it looks to be consistent between these two executors now again it looks like when we go to 1 million those performance improvements start to get a bit looser and they're actually not really consistently good on that bigger amount of items in this collection but we still have a tie for the fastest between these two approaches and the last one is an array on a value type so we have all of these for basically performing exactly the same which my suspicion is that they've been optimized to perform like a for loop on a span so that's why we have this similar performance and it's actually scaled linearly all the way to 1 million so an array of value types will actually perform better even all of them and all of them really are tied for the best performance so what conclusions can we draw out of this well it does appear that this approach is the fastest but it's equally as fast as a for loop on the span itself however the reason why this approach might be very useful and very attractive to you is because this thing isn't really only usable when iterating you can actually use this as an offset on a search space and this can be useful in many many situations again you can go ahead and see how Microsoft is using it for their own stuff to optimize things like string comparisons or good conversions I think so it's used in many many places now should you use this well unless you really really really know what you're doing and what you're optimizing I would say no I would stick with the for loop on the span it is equally as fast just not as flexible however you probably don't need that flexibility to begin with and if you do need it you probably know what you're doing so I can trust you with this knowledge but what do you think do you think that you would ever write C sharp like this and what do you think about the biggest argument of if I need to write C sharp like this why am I even writing C sharp anyway if I have to do with pointers and references and all this crazy stuff that CSC plus is doing leave a comment down below and let me know well that's all I had for you for this video thank you very much for watching especially thanks to my patreons making videos possible if you want to support me that's all you can find and Link description down below leave a like if you like this video subscribe the Bell as well and I'll see you in the next video keep coding
Info
Channel: Nick Chapsas
Views: 230,606
Rating: undefined out of 5
Keywords: Elfocrash, elfo, coding, .netcore, dot net, core, C#, how to code, tutorial, development, software engineering, microsoft, microsoft mvp, .net core, nick chapsas, chapsas, dotnet, .net, fast loop, c# fast loop, c# for vs foreach, for vs foreach, performance loop, loop performance, loop performance c#, .net loop, The weirdest way to loop in C#, The weirdest way to loop in C# is also the fastest
Id: cwBrWn4m9y8
Channel Id: undefined
Length: 12min 55sec (775 seconds)
Published: Thu Nov 24 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.