The fastest way to iterate a List in C# is NOT what you think

Поделиться
HTML-код
  • Опубликовано: 26 сен 2024
  • Check out my courses: dometrain.com
    Become a Patreon and get source code access: / nickchapsas
    Hello everybody I'm Nick and in this video I will show you all the way you can iterate a List in C# and then show you what is by far the fastest and most memory efficient way. You might have guessed where this is going :)
    Don't forget to comment, like and subscribe :)
    Social Media:
    Follow me on GitHub: bit.ly/ChapsasG...
    Follow me on Twitter: bit.ly/ChapsasT...
    Connect on LinkedIn: bit.ly/ChapsasL...
    Keep coding merch: keepcoding.shop
    #csharp #dotnet

Комментарии • 407

  • @capsey_
    @capsey_ 2 года назад +126

    Nick should get a shirt with "I Stan the Span" printed on it

    • @nickchapsas
      @nickchapsas  2 года назад +32

      Coming on a t-shirt near you twitter.com/nickchapsas/status/1523025560774987777

    • @joshstather3543
      @joshstather3543 2 года назад +9

      @@nickchapsas Coming on a t-shirt??? 😳😳

    • @foamtoaster9742
      @foamtoaster9742 2 года назад +3

      I would buy that

    • @Zatrit
      @Zatrit 2 года назад +1

      WriteLine'd on it*

  • @Ashalmawia
    @Ashalmawia Год назад +9

    .ForEach is not a LINQ method it is actually defined on List

  • @ristopaasivirta9770
    @ristopaasivirta9770 2 года назад +107

    Great benchmark.
    Good that you explained that the parallel versions are most likely faster when you actually do work inside the iterations.
    It always distracts me when you say "half the speed" when you mean "half the time" (ie. double the speed).
    I know it might be a language thingy, but it is really confusing at times.

    • @nickchapsas
      @nickchapsas  2 года назад +74

      Oh damn you are right. I was thinking it in my head in Greek. In English it doesn't really make sense.

    • @ZeroSleap
      @ZeroSleap 2 года назад +8

      @@nickchapsas Oh wait,so you are actually Greek huh?

    • @nickchapsas
      @nickchapsas  2 года назад +13

      @@ZeroSleap Yeap

    • @LordErnie
      @LordErnie 2 года назад +30

      He a bit geeky, he a bit Greeky

    • @MercifulDeth
      @MercifulDeth 2 года назад +4

      I did not notice that actually because in russian that means the same Nick meant.

  • @PavelOtverchenko
    @PavelOtverchenko 2 года назад +18

    Nick is a legend. Integrating "easter eggs" like 69, 1337 and 80085 always makes me smile

  • @Zindawg02
    @Zindawg02 2 года назад +8

    I've been a C# software engineer for a few years now and until this video I've never heard of a Span (outside the context of html lol). Going to be looking that up, great vid!

  • @FunWithBits
    @FunWithBits 2 года назад +6

    I like how this channel gives a clear statement on what will be answered in a moment. Example: (1) At 1:12 Nick says, "I'm going to put all the ways to iterate over a list here" (2) This allows the user to pause the video and try and think of ways. (3) Then click play and view the 7 different ways. Its great that something like "Feel free to pause the video and try...." as most viewers know this and would not do it anyway. Side note - I was able to only think of three ways. (for loop, and forearch loop, ToArray().Select(x=>x) )

  • @LoKSET
    @LoKSET 2 года назад +9

    A nice extension method is in order :D
    public static void FastForEach(this List source, Action action)
    {
    var span = CollectionsMarshal.AsSpan(source);
    foreach (var t in span)
    {
    action(t);
    }
    }

  • @IAmFeO2x
    @IAmFeO2x 2 года назад +43

    Great video as always! I also did some collection benchmarks back in April 2022, and i found something different than your benchmarks: for loops were considerably faster than foreach loops. The problem in your benchmark code might be that you access the private field instead of using a variable. foreach will automatically inline the field access to a variable, but for does not do that. In my benchmarks, foreach loops on List were 50% slower than for loops.

    • @nickchapsas
      @nickchapsas  2 года назад +47

      It looks like this got optimized in .NET 7

    • @MaxReble
      @MaxReble 2 года назад +3

      Yep, made similar benchmark tests and in dotnet 6 for 100k iterations and foreach took 75 us, for took 37us and Span Foreach 24us. Nice to see that dotnet 7 has many hidden performance boosts!

    • @IAmFeO2x
      @IAmFeO2x 2 года назад +2

      @@MaxReble Yep, can confirm, too: I reran my tests with .NET 7 RC1 and for and foreach loops are now nearly identitcal in speed. Still nearly twice as slow as iterating over arrays, spans, or ImmutableArray.

    • @aurinator
      @aurinator 2 года назад +1

      Should be parallel unless future iterations/loops are impacted by previous ones IMO. Going for the fastest synchronous/sequential approach is a great exercise, but independent iterations are the perfect Use Case to be done in parallel.

    • @daniel.marbach
      @daniel.marbach Год назад +1

      It might be a good idea to add a benchmark consumer type to actually consume the iteration result to make sure nothing gets optimized away

  • @ooples
    @ooples 2 года назад +13

    I love the humor of using 80085 as your seed and I'm not sure if anyone else caught this old calculator dirty humor

  • @kevinmartin7760
    @kevinmartin7760 2 года назад +2

    As an old guy, I want to add that, if you don't care about the order of iteration (and all the parallel examples illustrate this is the case here), you can run the index backwards, which avoids calling Count, Size, or Length on each iteration:
    var asSpan = CollectionsMarshal.AsSpan(_items);
    for (int i = asSpan.Length; --i >= 0; )
    {
    var item = asSpan[i];
    }
    Note that I explicitly declared i as a signed type so the loop termination condition can be satisfied.
    Many of the other examples (not using Span) also fail if the collection is changed during the iteration. The difference is that with the cases that use an enumerator you deterministically get a specific exception, whereas with the Span you just get mysterious behaviour (which is also true for the direct indexing loop).

    • @alexintel8029
      @alexintel8029 2 года назад +1

      Just a couple of days ago, I implemented a backward for loop similar to your example and it used a var for the index variable. I wonder if is it really worth using --i >= 0 ?

    • @kevinmartin7760
      @kevinmartin7760 2 года назад +2

      @@alexintel8029 It depends on the actual processor, but on the ones typically used nowadays comparing the result of a computation with zero is faster because no compare instruction is required. The instruction for the computation (in this case likely a decrement instruction) will set flags in the processor indicating if the result was zero, negative, or the computation produced signed or unsigned carry/borrow/overflow, so it can be immediately followed by a conditional jump.
      If you separate the decrement from the compare, a decent optimizing compiler should be able to relocate them so the conditional jump is still right after the compare, for instance treating:
      for (int i = x; i >= 0; --i) {...}
      as:
      int i = x;
      if (i >= 0)
      do {...} while (--i >= 0); // which can again decrement and conditionally jump with no compare
      instead of the more direct
      int i = x;
      while (i >= 0) { ...; --i; }
      However if your exit condition compares with a value other than constant zero, as in
      for (int i = 0; i

    • @alexintel8029
      @alexintel8029 2 года назад

      @@kevinmartin7760 Thanks for the explanation Kevin.
      I realise the beauty of your original example
      for (int i = asSpan.Length; --i >= 0; ) {...}
      It helps
      a) prevent accessing asSpan[asSpan.Length] which would lead to index out-of-bound error
      b) decrement the loop
      c) test for exit condition

  • @Tal__Shachar
    @Tal__Shachar 2 года назад +24

    Can't express enough how amazing and educational your videos are. Keep doing what you do!!

  • @urbanguest
    @urbanguest 2 года назад +7

    Your videos are the best and I have learned so much from watching your videos. Keep up the fantastic work! I've been coding for over 30years and I'm still learning new tricks, thanks!

  • @GarethDoherty1985
    @GarethDoherty1985 2 года назад +7

    This was a great video. I love your little deep dives into the C# language.

  • @jondoty
    @jondoty Год назад +1

    LINQ doesn't have a ForEach extension method. What's being used in the video looks like the ForEach method defined by List.

  • @paulembleton1733
    @paulembleton1733 2 года назад

    Didn’t know about Span(), never would have thought to look, foreach was already heaven, thank you.
    I was taught never add or remove items during a for loop but dd it anyway, then fast forward to writing multithreaded applications and foreach and Span() throwing an exception is a useful indicator of faulty design.

  • @hipihypnoctice
    @hipihypnoctice 2 года назад +11

    Interesting. I could use this to speed some things up. Seems pretty niche for most of what I do tho, but definitely an improvement where improvements can be made

  • @KineticCode
    @KineticCode 2 года назад +11

    i think the unsafe part is fine and expected, foreach breaks if you add/remove elements during a loop as well.

    • @RahulSingh-il1xk
      @RahulSingh-il1xk 2 года назад +2

      That's true. But what if we mutate objects of the list. Say, a person from List while looping. Foreach allows this - will this span approach too?

    • @PetrVejchoda
      @PetrVejchoda 2 года назад

      @@RahulSingh-il1xk Obviously not on values that you access during the iteration. What I am interested in is what happens if I mutate values, that are not accessed during the iteration.

    • @KineticCode
      @KineticCode 2 года назад +1

      Guys I think you can mutate objects :) its not a readonly span, just a span

    • @Crozz22
      @Crozz22 2 года назад

      because foreach breaks if the list is mutated then they should just make foreach compile into the unsafe part

  • @sergiom.954
    @sergiom.954 2 года назад +1

    That final console output is really good resume to keep in mind the use of iterations in c#. Very useful 👏👏

  • @itaylorm
    @itaylorm 8 месяцев назад

    Very detailed and helpful showing you the results of the typical options Andrew ones I had not seen before

  • @Evan-zj5mt
    @Evan-zj5mt 2 года назад +1

    Watching your videos makes is very humbling and makes me realise that I'm absolutely shit at my job!

    • @nickchapsas
      @nickchapsas  2 года назад +4

      Nah trust me you don’t need to know 99% of the stuff I show to be good at your job

  • @petrusion2827
    @petrusion2827 2 года назад +5

    Calling it before watching the video: CollectionsMarshal.AsSpan()
    Edit: Called it. I would've also liked to see a benchmark which uses the list cast to an IEnumerable, then the IEnumerator would be an interface variable instead of a stack struct which would slow things down because of dynamic dispatch.

    • @nickchapsas
      @nickchapsas  2 года назад +2

      Hey I can see that you skipped forward 👀

    • @petrusion2827
      @petrusion2827 2 года назад

      @@nickchapsas Oh yeah I didn't have time to watch the whole video from start to finish, I used RUclips's 10 second skips to go through the most relevant parts, but only after I made the comment :D
      Nice vid for sure

    • @QwDragon
      @QwDragon 2 года назад

      @@nickchapsas you haven't shown foreach on IEnumerable in the video.

  • @BadgersEscape
    @BadgersEscape 2 года назад +3

    Getting a local scoped reference to the array allows JIT to optimize away the range checks in the loop (technically also unroll but I don't think it does that). It's not possible for a list since there is no guarantee that other code somewhere wont change the length during our looping. But if you have an array, then length is fixed, and you can do a single if-check pre-looping instead of checking the bounds every iteration.

    • @TheMAZZTer
      @TheMAZZTer 2 года назад

      Interesting, this should also be possible for any foreach since the collection isn't allowed to change during the loop, but I guess .NET does not implement that optimization (yet).

  • @jongeduard
    @jongeduard 2 года назад +1

    Probably the absolute winner: on Stackoverflow I found an example of doing parallel work on a Span, but it involves unsafe pointers, since the Span type itself cannot escape to the heap and therefore Parallel calls on it are normally not possible.
    I searched for this because I was curious why this ultimate combination was not metioned in the video.

    • @pedroferreiramorais9773
      @pedroferreiramorais9773 2 года назад +1

      You can actually combine Parallel.ForEach and Span without pointers. I did some benchmarks and it was faster than all of Nick's implementations @ 1 million elements.
      The trick is using Partitioner.Create(0, list.Count) and passing it to Parallel.ForEach along with a closure around list that takes a Tuple as parameter and marshalls list to Span, then slices it using the tuple and finally iterates over it.

    • @jongeduard
      @jongeduard 2 года назад +1

      @@pedroferreiramorais9773 Oh great :), I really have to dive into that to understand how that works.
      Let's say that a very simple solution does at least not exist yet.

    • @pedroferreiramorais9773
      @pedroferreiramorais9773 2 года назад

      @@jongeduard well, you can create an extension method to encapsulate all the logic, but it loses much of the performance gain. It can still be better than sequential span iteration of very large lists/arrays, but if performance is the main concern, you often have to get your hands dirty.

  • @AbhinavKulshreshtha
    @AbhinavKulshreshtha 2 года назад +3

    80085, I see a man of refined culture. 😅☺️

    • @krccmsitp2884
      @krccmsitp2884 2 года назад +1

      The zip code of the Simpsons' home town Springfield.

    • @AbhinavKulshreshtha
      @AbhinavKulshreshtha 2 года назад +1

      @@krccmsitp2884 I didn't knew that.. I was thinking about the old calculator trick we used to do in schools during mid 90s, when we first got to use them.

    • @krccmsitp2884
      @krccmsitp2884 2 года назад

      @@AbhinavKulshreshtha Well, that's the other meaning. I know that trick too and what you wanted to indicate. :-)

  • @bmazi
    @bmazi Год назад

    Extra way: walk via unconditional (!) "for" loop, exit via catching "OutOfBounds" exception. Removes double-checking of bounds, but introduces overhead from exception handling. May outperform if the list is extremely huge (throw cost is constant and doesn't scale with items count).

  • @QwDragon
    @QwDragon 2 года назад +32

    ForEach is list method, not linq method.
    Span forbids only adding and removing of items, but not assigning.
    And also I don't like benchmarks that don't use data. Some optimizer can remove more than expected. You've shown IL, but it doesn't garantee jit won't change smth.
    9:26 How can 1 byte be allocated?

    • @VoroninPavel
      @VoroninPavel 2 года назад

      I assume there are cases when compiler (jit) can infer that it's safe to use span for list traversal.

    • @2003vito
      @2003vito 2 года назад +1

      malloc(1)

    • @protox4
      @protox4 2 года назад +3

      1 byte is allocated because the JIT allocates, and the benchmark picks it up. That allocation is removed in .Net 7. You can read more about it on the benchmarkdotnet repo.
      It's actually more than 1 byte allocated, but the benchmark divides it by how many iterations were ran.
      [Edit] Actually, the rogue allocation still seems to be showing up in Net 7, but the cause hasn't been looked into yet.

    • @QwDragon
      @QwDragon 2 года назад

      @@protox4 Thanks for pointing out is is averaged.

  • @CharlesBurnsPrime
    @CharlesBurnsPrime 2 года назад +3

    I did not expect to wake up this morning and be surprised by a genuinely faster way to iterate a list, one of the most common tasks in software development.

  • @jackp3303
    @jackp3303 Год назад

    There is one more way to iterate - using SIMD (Vector), which has lowest readability, but probably will have the best performance, because it's accelerated on hw level and CPU takes n items in processing at one CPU clock.

  • @stephencollis1453
    @stephencollis1453 Год назад

    I'm watching this for fun, it's legit fun to watch. Hat's off

  • @haxi52
    @haxi52 2 года назад +1

    I rarely work with List, would have liked to see benchmarks with lists of classes. I feel like the results would be very different.
    Also please add disclaimers to your performance videos. Some might get the idea they should be using span loops everywhere.

    • @nickchapsas
      @nickchapsas  2 года назад

      There is a similar performance improvement with other objects. The video does have a disclaimer too

    • @MaxReble
      @MaxReble 2 года назад

      I ran the test and on my machine results are as followed (with dotnet 7)
      List
      Iterate_ForEach | 49.71 us | 0.330 us | 0.276 us | - |
      Iterate_For | 40.59 us | 0.241 us | 0.213 us | - |
      Iterate_ForEach_AsSpan | 24.64 us | 0.129 us | 0.121 us | - |
      List
      Iterate_ForEach | 52.27 us | 0.634 us | 0.562 us | - |
      Iterate_For | 41.18 us | 0.477 us | 0.398 us | - |
      Iterate_ForEach_AsSpan | 25.33 us | 0.480 us | 0.426 us | - |
      So, I see a bigger difference between for and foreach as nick does, but the delta of for and foreach_asspan does not change.

  • @neotechfriend
    @neotechfriend 7 месяцев назад

    As always , thanks Nick

  • @borislavborislav5545
    @borislavborislav5545 Год назад

    You forgot one more exotic way:
    var numbers = Enumerable.Range(1, 1_000_000).ToList(); ;
    int idx = 0;
    start:
    if (idx < numbers.Count)
    {
    idx++;
    goto start;
    }

  • @tobyjacobs1310
    @tobyjacobs1310 2 года назад

    This is so much nicer than my in a pinch method:
    Compiled reflection accessing the array, then use that and the count to get a span. Probably a smidgeon faster too...
    Spans are amazing....

  • @karldavis7392
    @karldavis7392 2 года назад +1

    I use Parallel when the tasks are small in number and heavy. I have an app that does six similar tasks, each taking about 500 ms, and it's great. If you needed 3000 ms instead by doing 3000 tasks that each take 1 ms, the overhead of creating each instance makes it a close call. If it's 3,000,000 jobs that each take 1 us, then I definitely would not parallel.

  • @engineeranonymous
    @engineeranonymous 2 года назад +2

    If you are algorithm is loop heavy, you can use loop unrolling for more speed.

  • @alphaanar2651
    @alphaanar2651 2 года назад +1

    I KNEW IT WOULD AGAIN BE SPAN RIGHT FROM THE BEGINNING LMAO

  • @egoegoone
    @egoegoone 2 года назад +3

    Cool video as usual Nick! Could a rule of thumb be something like:
    - few items with no mutations: span
    - few items with mutations: for/foreach
    -many items with few operations: span/for
    - many items many operations: parallel
    ?

  • @TheMAZZTer
    @TheMAZZTer 2 года назад +5

    You can't mutate the collection in any foreach anyway so there's no downside to using the span method it would seem to me. So if you need to mutate the list you're using for instead of foreach anyway and just can't use the span.
    Very cool!

    • @jasonx7803
      @jasonx7803 2 года назад +1

      If you're running parallel code something else might be mutating the collection.

    • @lnagy88
      @lnagy88 2 года назад +1

      @@jasonx7803 Then maybe you shouldn't use a list and try something more thread safe.

    • @PelFox
      @PelFox Год назад

      It's limited to List though, wished it would have worked for IEnumerable or IReadOnlyCollection. Very rarely do I need to iterate a List without doing some work on the data, then I would use a IReadOnlyCollection or IEnumerable.

    • @jasonx7803
      @jasonx7803 Год назад

      @@lnagy88 I'm just responding to the idea that "there's no downside". Using a forEach isn't thread safe just because it can't modify the data, you still have to make sure nothing else is modifying it.

  • @miguelfajardo8236
    @miguelfajardo8236 Год назад

    What a great video and information for iterating a List !! I think that this works with a List of object that has plenty of properties...

  • @zacky7862
    @zacky7862 Год назад

    Oh wow! didn't know about this. Thank you so much

  • @pauliusdotpro
    @pauliusdotpro 2 года назад +10

    You cant update collection while doing regular foreach loop either. How come in this case it is not 'unsafe'? How the safety is different when compared to span?

    • @NEProductionE
      @NEProductionE 2 года назад +1

      If you have a list items and you wanna foreach it with mutate just do foreach( var item in items.ToList()) and then you can mutate

    • @deamit6225
      @deamit6225 2 года назад

      But you shouldnt mutate a list while your iterate over it anyways

    • @stianramstad9683
      @stianramstad9683 2 года назад +3

      The span will not throw an exception that the collection is modified.
      Example:
      var list = Enumerable.Range(1, 5).ToList();
      foreach (var item in CollectionsMarshal.AsSpan(list))
      {
      if (item == 1)
      {
      list.RemoveAt(3);
      }
      Console.Write(item);
      }
      Will print 12355, without giving an exception.

    • @billy65bob
      @billy65bob 2 года назад +2

      It also depends how you modify the list in question.
      If you replace items in the list, it's not a big deal; you'll see the changes, and nothing will explode.
      CollectionMarshal even gives you a ReadWrite span, this is an expected and intended use case.
      If you remove things, you'll get odd and inconsistent results like Stian has demonstrated; In his example you are reading data that is out of range of normal indexers.
      This data is garbage; it's either stale, or null (depends on whether references are involved) and the span will not be aware of the new range.
      Now if you do add or insert, you'll be fine until the backing store has to expand.
      You'll see the inserted data (up to the original size) until that expansion, but once that expansion happens I have no idea what the results will be.
      It depends on whether Span keeps the original backing store alive, and that I'm not sure of...
      if it does, you'll stop seeing changes in your span, if it doesn't, then it'll either explode in your face, or expose you to C style bugs where you end up reading memory you're not supposed to; the latter is very bad since it leads to issues like OpenSSL's heartbleed.
      The short of it is: if you do ANYTHING that can affect the size of the backing store, you are playing with fire.
      While you'd normally use an array with Array.Fill for this, this example is completely above board.
      public static void ResetCounters(List counters)
      {
      var span = CollectionsMarshal.AsSpan(counters);
      foreach (ref var counter in span)
      counter = 0;
      }

  • @AaronMolligan
    @AaronMolligan 2 года назад +1

    I am only 2 weeks into the learning how to code and actually learning c#. What you talking about and showing looks great and fun but it's to much for my brain is right now. Your videos are cool and very informative but just to advanced or geared to seasoned coders. But nice videos, one day I'll fully understand all the things you explain in your vids.

    • @matthewjohnson3656
      @matthewjohnson3656 2 года назад +1

      I’ve been programming for ten years and I have never heard of a span before

    • @davestorm6718
      @davestorm6718 2 года назад

      @@matthewjohnson3656 Same here (25 years!) and just heard of a span a month ago (that isn't )!

  • @matejakendereski4264
    @matejakendereski4264 Год назад

    Everything is cool! Thanks!

  • @DaveGouda
    @DaveGouda 2 года назад

    This was really interesting. I've never seen the AsSpan methods before. I honestly had to look up what a Span was lmao.

  • @spacetravelnerd6058
    @spacetravelnerd6058 2 года назад

    Great video! I certainly could have used this on previous projects but will make sure it gives me the benefits I need for my current ones.

  • @coding-gemini
    @coding-gemini 2 года назад

    Very interesting to learn this, I could use this in my project. Thanks Nick

  • @hamedsalameh8155
    @hamedsalameh8155 Год назад +1

    This is really amazing Nick! Even in the things that seem so basic in simple, we are finding hidden gems!

  • @nickst0ne
    @nickst0ne 2 года назад

    When a program's performance tests are below client's expectations, would you always go for a Span refactorization?
    ...assuming that no major blunder was made like a bad algorithmic complexity.

  • @diadetediotedio6918
    @diadetediotedio6918 2 года назад

    I will give you a little tip, on the List as Span method you can actually use it to modify the items with ref, like:
    var listSpan = CollectionsMarshal.AsSpan(list);
    foreach(ref var item in listSpan)
    {
    // You can modify 'item' here even if it is a struct
    }

    • @nickchapsas
      @nickchapsas  2 года назад

      You don’t need ref to modify the items. You can just modify them. They are still references

    • @diadetediotedio6918
      @diadetediotedio6918 2 года назад +3

      @@nickchapsas
      No, if they are structs you will surely need ref, and if you want to "modify" immutable records too.
      Think in things like:
      foreach(ref var itemStats in item.Stats)
      {
      itemStats = itemStats with { Speed = 10 };
      }
      You cannot do anything like that without refs.

  • @deus_nsf
    @deus_nsf 7 месяцев назад

    This is super interesting! I never learned about "spans" in any school, I don't even know what it is! I will eventually google it but could you elaborate quickly in case my laziness gets the better of me? 😅Thanks!

  • @docleoyeo
    @docleoyeo Год назад

    This helped a lot thank you

  • @rmcgraw7943
    @rmcgraw7943 Месяц назад

    You should have covered doing the Parallel version of the loops with span. items.AsParallel.AsSpan… .

  • @tronaitor0
    @tronaitor0 Год назад

    Love these kind of videos, and so well explained!

  • @ВолодимирДідух-н3р
    @ВолодимирДідух-н3р 2 года назад

    It was funny when I rolled my eyes before you said “Of course it would be a span” and started giggling 😂😂

  • @justengineering1008
    @justengineering1008 2 года назад

    To complete the big picture, You can add Enumerator + While( list.MoveNext()){bla-bla-bla}
    some tests say that it more efficient than foreach/for but not so efficient as a span

  • @gergelycsaba5008
    @gergelycsaba5008 2 года назад +1

    1:35 Nice seed value :)

  • @CricketThomas
    @CricketThomas 2 года назад

    The puts so much perspective on things 😅

  • @mrcalico7091
    @mrcalico7091 2 года назад

    "Half the speed" lol, Your C# is a lot better than your physics. I think you mean "half the time, twice the speed." Love your videos, great work!

    • @nickchapsas
      @nickchapsas  2 года назад

      Oh damn, I meant half the time LOL

  • @chicoern
    @chicoern Год назад

    Imma steal this to my PowerShell

  • @kentjohnson6844
    @kentjohnson6844 2 года назад +2

    foreach will throw an exception if the list is modified while itterating as well, so the span is no worse in that respect.

    • @MrBunt
      @MrBunt 2 года назад +2

      the span won't throw though, so you might not catch your problem as fast

    • @kentjohnson6844
      @kentjohnson6844 2 года назад

      @@MrBunt that makes sense.

  • @benoitb.3679
    @benoitb.3679 2 года назад

    Hahahahahaha "I'm just going to add a seed..." (80085) that made me laugh so much, man am I childish.

  • @micmacha
    @micmacha 2 года назад

    Thank you, I literally didn't even know about spans.

  • @ayudakov
    @ayudakov 2 года назад

    Thank you!

  • @shahfaisal3923
    @shahfaisal3923 2 года назад

    Just amazing Sir.
    Lots of Love and Respect from Afghanistan.
    Please pray for my country to get rid of terrorist taliban.

  • @elpe21
    @elpe21 Год назад

    foreach statement cannot operate on enumerators of type 'Span.Enumerator' in async or iterator methods because 'Span.Enumerator' is a ref struct. Has that changed in .Net 7?

  • @rogerdeutsch5883
    @rogerdeutsch5883 2 года назад

    Fantastic video, great info and very clear. Learned a lot. Subscribed.

  • @Sky4CE
    @Sky4CE 2 года назад

    That is awesome! Thanks Nick

  • @jakobkellett
    @jakobkellett Год назад +1

    "I'm going to add a seed here." - casually puts 80085 (BOOBS) LOL. Thanks for the small laugh.

  • @rusektor
    @rusektor 2 года назад

    As a side note, there's new Random.Shared property which returns thread-safe Random

    • @nickchapsas
      @nickchapsas  2 года назад

      I don’t need a thread safe random, I need a deterministic one

  • @GregUzelac
    @GregUzelac 2 года назад

    Outstanding comparison. 5 stars

  • @ItsTheMojo
    @ItsTheMojo 2 года назад +2

    The only approach that doesn't throw an exception if the collection changes, at least as far as I know, is a for loop. Anything that uses an iterator will throw because the MoveNext method checks the version. So that covers foreach and the List.ForEach methods at least. The parallel ones almost certainly won't handle a collection change during iteration. Most of the time,
    changing a collection while iterating over it in any way is undesirable.

  • @redguard128
    @redguard128 2 года назад +7

    It was as expected. Nothing beats the standard WHILE loop except doing things in parallel - which comes with a lot of downsides.

  • @nove1398
    @nove1398 2 года назад

    Very interesting stats here

  • @franciscovilches6839
    @franciscovilches6839 2 года назад

    Very interesting. Thanks for this!

  • @baulind5297
    @baulind5297 2 года назад +9

    80085

  • @brunodossantosrodrigues5049
    @brunodossantosrodrigues5049 2 года назад

    How much time did you train to talk so fast and clearly at the end of the video? I really thought that I knew how to iterate... thanks for always taking our code to the next level

  • @swordblaster2596
    @swordblaster2596 2 года назад

    I doubt I'll ever use it, but quite amazing.

  • @cdarrigo
    @cdarrigo 2 года назад

    Excellent find. Thank you

  • @somebodystealsmyname
    @somebodystealsmyname 2 года назад +1

    I see what you did with the Seed :D

  • @Meta0Riot
    @Meta0Riot 2 года назад

    Awesome video. Always learn something new.

  • @slipoch6635
    @slipoch6635 2 года назад

    Always great info man.

  • @dwiprajdutta
    @dwiprajdutta 2 года назад +1

    If I understood correctly, Mutating the objects in the list won't be a problem unless and until I am not using Add() or Remove() like methods on the list itself "WHILE ITERATING".

    • @nickchapsas
      @nickchapsas  2 года назад +1

      Correct

    • @dwiprajdutta
      @dwiprajdutta 2 года назад +1

      @@nickchapsas Ok thanks. It will be my next default iterating technique just like "sealed" class. Thanks again.

  • @weicco
    @weicco 8 месяцев назад

    Just add pointer to the head of the list and move the pointer sizeof(list_item) forward until you are at the end of the list :)

  • @jeffreyblack666
    @jeffreyblack666 Год назад

    I would say that with the error listed, it is only the 100 item list that is different for the For and ForEach.
    You can even see that when you run all of them options, you get the order the other way around for the 1 million item list.

  • @vladkorsak2163
    @vladkorsak2163 2 года назад

    Good point man. Thanks for the video.

  • @beecee793
    @beecee793 2 года назад

    Great video, glad I found you.

  • @oneeyejack2
    @oneeyejack2 2 года назад

    I discovered that you should avoid using list in the first place, especially for "returning" list of things.. Passing a delegate (a callback) to your list generator and having it calling the callback is much faster that storing a list and iterating it after. Plus you can generically transform delegate (for instance transform any delegate into a filtering delegate that calls or don't call the first one)

    • @lnagy88
      @lnagy88 2 года назад

      yield return wouldn't have the same effect? I kinda have nightmares with callbacks :)

    • @oneeyejack2
      @oneeyejack2 2 года назад

      ​@@lnagy88 I never had the courage to learn how to use it... But since it is returned as a IEnumerable object, I suppose it create a collection of some sort in memory.. so I don't trust the optimization.
      You can create operators of delegate : a function that take delegates and return a new delegate that use or not the given ones in various ways.. You can basically create complex "requests" using delegates. It not just "callbacks".. you can you it instead of iterators etc.. For instance explore a tree recursively given a callback.. and then use this function to pass any action to do with each node..
      you can create a delegate operator that is a generic filter (using a set) to check each value and call it only once and return the new callback.
      I think it's very performant because most of the data stays in the stack, and there's only a few heap object creation (the delegate themselves are objects)

    • @oneeyejack2
      @oneeyejack2 2 года назад

      @@lnagy88
      for instance i've done a code parser
      When I was young I would have used something like
      List Parse(int offset);
      now I do
      delegate void Result(Expression,int);
      delegate void Parser(int,Result);
      And you can make a "operator"
      that combine parsers :
      Parser ParseOper(Parser A,Parser B,string oper)
      {
      return (int ofs,Result call)=>
      {
      A(ofs,(Expression xa,int nx)=>
      {
      call(xa,nx);
      if (readoperator(ref nx,oper))
      {
      B(nx,(Expression xb,int nb)=>
      {
      call(sumexpression(xa,xb),nb);
      });
      }
      }); }
      }
      The resulting parser use the parser A, if it has a result it test for the presence of a string, then it use the parser B, and the callback is called with the result A+B
      You can then basically create a parser than Parse C# expressions like this
      Parser NumOrVariable=[..... ];
      Parser prod = ParseOper(NumOrVariable,NumOrVariable,"*");
      Parser sum = ParseOper(prod,prod,"+");
      Parser dif = ParseOper(sum,sum,"-");
      The beauty of it is that alternate results that will be discarded are never stored and organized together..

  • @syriuszb8611
    @syriuszb8611 2 года назад

    At the beginning I literally thought "I have no idea what it will be, but for sure it will use span!".

  • @Grimlock1979
    @Grimlock1979 2 года назад

    If you look at the source code for the ForEach method (which is not a Linq method btw), all it does is use a for loop. I don't see why it would be so slow.

  • @alexkazz0
    @alexkazz0 2 года назад +1

    Span always wins, eh? :) Anyway you should never mutate a list while you iterate over it. Thanks Nick!!

    • @nothingisreal6345
      @nothingisreal6345 2 года назад +1

      It is direct memory access - almost like C/C++. C/C++ use pointer arithmetic's which is even faster.

  • @lancemarchetti8673
    @lancemarchetti8673 2 года назад

    Brilliant!

  • @Faygris
    @Faygris 2 года назад

    Your channel is a pot of gold for me as a bad C# developer ⭐

  • @rmcgraw7943
    @rmcgraw7943 Месяц назад

    U should also have mentioned that using parallel can create concurrency issues.

  • @ivaniliev93
    @ivaniliev93 2 года назад

    Amazing stuff, thanks!

  • @rainair402
    @rainair402 2 года назад

    I wonder if you know, that Microsoft did implemented an implicit cast for ReadOnlySpan which is safe then:
    [Benchmark]
    public void Foreach_Span()
    {
    System.ReadOnlySpan spanList = CollectionsMarshal.AsSpan(_list);
    foreach (int item in spanList)
    {
    }
    }

    • @JuvStudios
      @JuvStudios Год назад

      This is also not safe because you could modify _list in between.

  • @chiragdarji1571
    @chiragdarji1571 2 года назад +3

    hi, great video as usual. Do you take topic suggestions? parallel.foreach vs parallel.foreachasync pls .. :)

  • @lordicemaniac
    @lordicemaniac 2 года назад +2

    how about parallel foreach span? :D

  • @ceosmangumus
    @ceosmangumus 2 года назад +1

    Foreach also has the same limitation, can't add or remove from the list, then we can replace each "foreach" with span version?

  • @nicolasmaluleke3343
    @nicolasmaluleke3343 2 года назад

    Learning new tricks every day here

  • @inayelle
    @inayelle 2 года назад

    List's ForEach method is actually a part if Linq, it's a part if List itself :)