I'm probably in the minority here, but I love the old params keyword. It made it fun to have a `params string[]` and kind of have my method act like Main(int args[]) and have infinite strings passed in for uses like passing in rules, or `params Func expressions` for passing in expressions in something like WPF where we might bind to properties using the arrow syntax (and a little bit of hacking a base abstract model class). I like this NumbersCollection impl you wrote. I normally don't mess w/ iterators or *Collections, but since we can use params to hijack the cotr, that's pretty sick.
@@nicholaspreston9586 I had no problem with params as well. But, to be hones I used it rarely and in places where it was not crucial at all. I do not think It will change my mind about the use cases where I use. Hmmm, I am thinking if it can be useful at functional programming and/or at composite pattern... we will see...
@@zbaktube "Hmmm, I am thinking if it can be useful at functional programming and/or at composite pattern" Could be. I like experimenting with `params Expression []` for things like multiple regex patterns for a class I'm extracting, where each expression binds to a specific property in that class. There may be a way to use it with monads or currying functions. I wouldn't know, b/c I'm still wrapping my head around those.
Thanks for correcting, I noticed you saying it indeed. The array itself uses a GC heap allocation at once, not each integer inside it indeed. The List performed a bit more inefficient in your example, which is actually something Microsoft can possibly improve a bit more if they don't already do it, if they generate the generic List in IL with a predefined capacity (since List has a constructor for that) in cases where the number of arguments is also known to the compiler, then it should perform practically equal to a regular array.
@@jongeduard "(since List has a constructor for that" Correct me if I'm wrong, but doesn't List default to 0 length/count anyways, regardless of cotr. param?
Thank you a lot, I just have one request, can you please Add `BaseLine=true` to one the `Benchmark` attributes, so the results will contains the "Ratio" column, and then we can see how they are faster (or slower) than each other. Thank you
Array elements do not get boxed, this is entirely incorrect. They would be boxed if you had an object[] or ValueType[]. Even if you used generics, because generic variants are compiled per the generic type, they would still not get boxed. This is very misleading.
What Nick probably actually meant (or of what probably was actually think of) is a `params object[]` , like in string.Format. There of course all integers would be boxed, because the array type is of object. Meaning first an array is allocated on the heap, then every single integer would be again allocated on the heap. But if you have an int[]-Array than of course only the array is allocated on the heap, the integers then get copied into the array (on the heap). But the integers don’t get boxed in this scenario.
As far as I remember, `SumNumbers(param int[] numbers)` in this particular case value types (ints) won't be boxed but rather inlined, stored one after another (like on stack) without any boxing overhead(like reference to type (MTTable or something) and syncblock)
In one solution, I was annoyed by use of `params`. I did quick analysis and guess what - 99% of the time, method was receiving 2 values, and in single call just 3. And no other call to that method was done. It was quick refactoring :) ... Using spans is great addition.
2:45 "an array C# is a reference type _and all the values in that reference type will be boxed_" wait, what?? I don't think so. Not in the sense that each `int` being boxed into an object allocated alone on the heap. It will just be an objectm, the array of ints, allocated on the heap but each element will just be an int in the array not a separate object themselves.
Yeah same. To my understanding this is exactly the scenario Interfaces are made of. "This is a contract, your class has to fulfil it to be useable for X" In this case X means "be useable as a params type".
"foreach" doesn't have to be an enumerable, it just needs to support enumerable semantics. Quack quack. The language designers are smart, there are generally good reasons for the how's and why's of what they do
It's the exact same rules as for collection initializers in this regard. And I'd guess (have to look up the design notes) that the whole feature is very much inspired by collection expressions.
2:50, I'm sure you're aware, but values in an array are typically not boxed in .NET. They are blitted. If they were boxed, than for each int in the array you'd have to unbox them. What you probably meant is that the array lives on the heap and the values live there as well (as opposed to on the stack). Regardless, great video, as always ❤
Every time I have a method with an X number of parameters I always make overloads for the first 1~3 and then use params for 4+ exactly because your channel made me paranoid about performance (I'm not complaining tho).
Just write clean code. If performance is an issue, AND the slow code is slow because of this or that feature, then complicate that code. Otherwise make your life, and other's, easier. Don't preoptimize.
@@rafazieba9982 Having simpler code makes it more likely that you end up with secure and correctly working code. Simplicity is not a result of 'lacking the required effort to write good code'. I may be reading too much into it but to me it sounds that´s what you are implying here.
I was sure, that the video will talk about `finally` keyword. I like the new `params` feature a lot. In case of `IEnumerable` what actual type is created?
The same as when you use a collection expression, like: "IEnumerable a = [1,2,3]". The actual type is an array, but it is wrapped inside another object to make it readonly.
Hi Nick, thank you for the great content! I'm having an issue with .net core applications running in a linux environment where gen2 memory isn't getting cleared by the GC. Seems to be a known issue, Any pointers?
I find that with the introduction of collection expressions the params keyword became much less useful as now you save only two characters: `MyMethod(param1, param2, param3)` with the params keyword vs `MyMethod([param1, param2, param3])` with Collection expressions.
Something you left out, params with a collection type, can still accept that collection as a single parameter. Which is awesome because that should mean that spans can just be offered as a parameter as well as a set of values. :) PARAMS ALL THE SPANS!!! :P
> all the values in a reference type will be boxed no, value type instances are not boxed when you put them into an array There's not much value in `params` anymore when you have the [] syntax at your disposal. Why writing `Method(params ReadOnlySpan values)` and call it as `Method(1, 2, 3)` when you can do `Method(ReadOnlySpan values)` and `Method([1, 2, 3])`? Also I personally don't understand why the choice was made to require Add(T) method on the type. Why not a static FromSpan(ReadOnlySpan)? This approach loses statically available collection size info.
I am pretty confused with the difference between Array and Enumerable. I thought that for an arguments' set the compiler would have chosen the same background structure to pass the values. It make sense to use IEnumerable since it's the base interface for all other types. Does this difference have anything to do with the way the set is read ?
According to the lowered code in SharpLab it does use an Array but it wraps it in a generated type that actually implements IEnumerable and whatnot so you got some overhead from that and also calling methods on an interface is generally slower because of dynamic method binding/virtual function calls
The question on IEnumerable is that it is an iterator that have it's own special way of functioning, it firstly has to alloc an IEnumerator when iterating and then it need to call a bunch of methods each loop iteration to make it work. Arrays on the other hand are a special type in the CLR that is very optimized, so lowering it to the IEnumerable interface which is more generic will make things slower. What I would ask is if using a generic param would work in this case, and if this would make it faster as JIT would have generic type information embedded in the method itself and would allow you to specify an IEnumerable as requirement (generic constraint) at the same time.
An array of integers doesn't box every integer though. It allocates a heap object because the array is a heap object. The array, as a heap object, requires 4 bytes of heap for every int + 4 bytes to store the array length, and idk if C# allocates more for other array metadata. The integers are stored in the array, but they are not individually boxed. Instead it's one bigger box (the array) storing all of the integers. It's a single heap allocation.
ReadOnlySpan has no Add function does it? I assume there are some very type specific optimisation around here. I just wonder how it performs on macs or linux...
Allowing Span significantly faster"? In what use cases, for a regular developer, is span 'significantly' faster than an array? int[] also does not have any boxing.
The worse performance of SumEnumerable is interesting in particular to me, because C# could theoretically internally just send an array as that inherits IEnumerable, which would be much more efficient even with boxing. This to me feels like it's not doing that and it's doing something else entirely.
Edit: The Span/ReadOnlySpan struct contains only a reference and a 32 bit length, so it's no more than 16 bytes on the stack. Original message: It'll only stackalloc when you "use the params feature".. e.g. var sum = Sum( 1,2,3,3,5,6 ); int Sum( params ReadOnlySpan values) { .... } A Span can be 2^31 items when it's referencing items in an array. var arr = new int[int.MaxValue]; var span = arr.AsSpan(); var sum = Sum( span ); So unless the caller puts an awful lot lot arguments in the function it won't overflow the stack. E.g. var sum = Sum( 1,2,4, ..., 500_000); Thinking about it a bit more, I'm pretty sure they'll check how many arguments are passed and heap allocate beyond a certain point.
Hey there, the feature looks nice however I have a small clarification: This "In C# Arrays are reference types and all values of that array will be boxed" is a bit misleading. th-cam.com/video/SlePiL1Azsg/w-d-xo.htmlsi=KQ0mIIXezzHSoo7j&t=165 It is true that in C# Arrays are reference types And it is true that the array itself and all values are allocated on the heap. However the values within this array are NOT boxed themself. The boxing of the values themself only happen if you use an object[].
I think you are misinterpreting him, he can say all values are technically boxed since they are now in the heap instead of being in the stack (even if they are contiguous, because I don't think there is nothing inherent to the definition of boxing that requires it to be non-contiguous).
@@diadetediotedio6918 yes that’s how I’m interpreting. Its the same as Rusts Box::. Boxed is a term for non stack allocated stuff. Every reference type in C# leaves on the heap and only the pointer to that “box” is on the stack
@@metaltyphoon Ah, I got your reasoning, I know rust as well. But this terminology cannot be applied in C# in the same sense it is in Rust, because in Rust all types are stack-allocated by default until they are boxed (either with Box, Rc, Arc, etc), and in C# all classes are heap-allocated by default (so the term "boxing" would not make sense as their allocation behavior is intrinsic to their declaration and usage). Boxing in C# happens with structs (primitives and user defined types) and in this regard it works reasonably the "same" way as in Rust.
Often you need to pass already existed collection info params-accepting method, and so need to make an array from existed List or IEnumerable. And it always was like - what are you doing here. So even from that perspective it's already good. Quite surprised with IEnumerable results. Perhaps array and list faster because of full array (and internal array of list) allocated as one flat chunk of memory, so cpu can predict code flow? Or it's because some 'context switching' between called and caller methods?
So what actually happens in the IEnumerable case? It has to be passed as something concrete like an array or List anyway, why is the performance so much worse?
it generates a wrapper around the array (internal sealed classz__ReadOnlyArray : IList, IReadOnlyList) the reason why IEnumerable is so much worse against array/list is that the benchmark measures 2 things!! (collection creation time + sum) if it only measured the time to create the collection, the performance would be somewhere between array and List +the Sum method is optimized for T[] and List and can add several numbers at once thanks to SIMD, while z__ReadOnlyArray uses a non-optimized method that adds elements one at a time
Just a little comment when you said that Span passes the numbers by value. This is not entirely accurate, the Span itself is a value object, yes, but the values are passed by reference because it’s kind of like a pointer that references the integers in the stack in the calling method.
Yeah I was curious about that. What does he mean exactly? The span is allocated on the stack. So the pointer to the first memory location is on the stack? And that pointer gets passed by value, meaning copied, when passed as a parameter?
@@johnhershberg5915 The Span is a structure so it is on the stack and passed by value, he was right about that, but the data inside is not copied with the span, only a reference to it.
hey Nick, I just want to tell you that the benchmark you're showing us is useless, it measures 2 things: 1. collection creation time 2. the duration of the Sum method it doesn't sound like a problem but don't forget that the Sum method is optimized for array and List, so it gives biased results!! (maybe instead of the Sum method you could just get the first element in the collection to simulate some work)
1. Collection creation time is intentionally part of the benchmark. That’s the whole point 2. Using sum or a loop will produce the exact same results for all types in this benchmark
@@pagorbunov yeah but you cant have exceptions for one thing and not others. Beside there were proposals to allow general duck typing in C#. Mads talked about it in a podcast
@@metaltyphoon agree but the number of cases when you’re like what is going on will increase cause who knows what side effects that Add method could trigger
This isn't even impressive nor amazing. Sure a decent QOL to expand target types params works with is fine enough. But it was already useful before. So much overhyping
You have to remember Nick's background industry is extremely perf conscious, so anything that saves CPU cycles (like no allocations) can make a huge overall perf and scalability improvements.
C# is highly backward compatibility. There would have to be an extremely important reason to remove GOTO. Also, goto is used in several implementations in the BCL. So, yea, that's not going anywhere.
Just a correction. Arrays don’t box their values unless you use an array of objects.
I'm probably in the minority here, but I love the old params keyword. It made it fun to have a `params string[]` and kind of have my method act like Main(int args[]) and have infinite strings passed in for uses like passing in rules, or `params Func expressions` for passing in expressions in something like WPF where we might bind to properties using the arrow syntax (and a little bit of hacking a base abstract model class).
I like this NumbersCollection impl you wrote. I normally don't mess w/ iterators or *Collections, but since we can use params to hijack the cotr, that's pretty sick.
@@nicholaspreston9586 I had no problem with params as well. But, to be hones I used it rarely and in places where it was not crucial at all. I do not think It will change my mind about the use cases where I use. Hmmm, I am thinking if it can be useful at functional programming and/or at composite pattern... we will see...
@@zbaktube "Hmmm, I am thinking if it can be useful at functional programming and/or at composite pattern"
Could be. I like experimenting with `params Expression []` for things like multiple regex patterns for a class I'm extracting, where each expression binds to a specific property in that class.
There may be a way to use it with monads or currying functions. I wouldn't know, b/c I'm still wrapping my head around those.
Thanks for correcting, I noticed you saying it indeed. The array itself uses a GC heap allocation at once, not each integer inside it indeed.
The List performed a bit more inefficient in your example, which is actually something Microsoft can possibly improve a bit more if they don't already do it, if they generate the generic List in IL with a predefined capacity (since List has a constructor for that) in cases where the number of arguments is also known to the compiler, then it should perform practically equal to a regular array.
@@jongeduard "(since List has a constructor for that"
Correct me if I'm wrong, but doesn't List default to 0 length/count anyways, regardless of cotr. param?
Start putting what you're actually gonna talk about in video title because it is hard to alter find it.
This Will Blow Your Mind They Finally Fixed This Insane Feature!!
Don't worry, the titles get updated with searchable titles after the inital boost of the video :)
Clickbait ftw :D
Ha, I saw the title and immediately thought it was going to be about the sealed keyword :D
In the meantime the DeArrow extension can help a bit, the title is simply renamed to Params - Fixed in C# 13
Thank you a lot, I just have one request, can you please Add `BaseLine=true` to one the `Benchmark` attributes, so the results will contains the "Ratio" column, and then we can see how they are faster (or slower) than each other. Thank you
At 2:52 you say “an array is a reference type and all values in that array will be boxed”. I don’t think it’s correct that the values will be boxed
Array elements do not get boxed, this is entirely incorrect. They would be boxed if you had an object[] or ValueType[]. Even if you used generics, because generic variants are compiled per the generic type, they would still not get boxed. This is very misleading.
What Nick probably actually meant (or of what probably was actually think of) is a `params object[]` , like in string.Format. There of course all integers would be boxed, because the array type is of object. Meaning first an array is allocated on the heap, then every single integer would be again allocated on the heap.
But if you have an int[]-Array than of course only the array is allocated on the heap, the integers then get copied into the array (on the heap). But the integers don’t get boxed in this scenario.
@@martinprohn2433 Correct. Misleading nonetheless.
Nick has corrected this in the pinned comment.
As far as I remember, `SumNumbers(param int[] numbers)` in this particular case value types (ints) won't be boxed but rather inlined, stored one after another (like on stack) without any boxing overhead(like reference to type (MTTable or something) and syncblock)
In one solution, I was annoyed by use of `params`. I did quick analysis and guess what - 99% of the time, method was receiving 2 values, and in single call just 3. And no other call to that method was done.
It was quick refactoring :) ... Using spans is great addition.
then someone tries it for 4 parameters or collection and it does not compile anymore
@@pagorbunovYou can leave the params overload. Or just add another one if you happens to need three parameters. YAGNI
2:45 "an array C# is a reference type _and all the values in that reference type will be boxed_" wait, what?? I don't think so. Not in the sense that each `int` being boxed into an object allocated alone on the heap. It will just be an objectm, the array of ints, allocated on the heap but each element will just be an int in the array not a separate object themselves.
I don't really like the duck typing with the Add function. I would prefer an interface for that instead.
Agree 💯, it's horrible... Way worse than the params keyword 😂
Yeah same. To my understanding this is exactly the scenario Interfaces are made of. "This is a contract, your class has to fulfil it to be useable for X" In this case X means "be useable as a params type".
"foreach" doesn't have to be an enumerable, it just needs to support enumerable semantics. Quack quack.
The language designers are smart, there are generally good reasons for the how's and why's of what they do
In that case, you can implement ICollection.
It's the exact same rules as for collection initializers in this regard. And I'd guess (have to look up the design notes) that the whole feature is very much inspired by collection expressions.
Your benchmarks showed that List was about twice as fast as IEnumerable. Isn't a List also an IEnumerable? Why the difference in performance?
probably dynamic dispatch
2:50, I'm sure you're aware, but values in an array are typically not boxed in .NET. They are blitted. If they were boxed, than for each int in the array you'd have to unbox them. What you probably meant is that the array lives on the heap and the values live there as well (as opposed to on the stack).
Regardless, great video, as always ❤
do the values of an array live on the heap even if they're value types?
@@idk-jb7lxyes they do
@@idk-jb7lx Yes. Array memory is allocated on the heap, whether they're ints or reference types
Every time I have a method with an X number of parameters I always make overloads for the first 1~3 and then use params for 4+ exactly because your channel made me paranoid about performance (I'm not complaining tho).
Just write clean code. If performance is an issue, AND the slow code is slow because of this or that feature, then complicate that code. Otherwise make your life, and other's, easier. Don't preoptimize.
@@RafaelConceicao-wz5ko Sure. Add performance later. Add security later. :) :) :)
@@rafazieba9982 Having simpler code makes it more likely that you end up with secure and correctly working code. Simplicity is not a result of 'lacking the required effort to write good code'. I may be reading too much into it but to me it sounds that´s what you are implying here.
@@RafaelConceicao-wz5ko too often in real world "later" equals "never"
Holy! they finally did it! I've been waiting for something like this from C# for over 20 years now 😂
I was sure, that the video will talk about `finally` keyword.
I like the new `params` feature a lot.
In case of `IEnumerable` what actual type is created?
The same as when you use a collection expression, like: "IEnumerable a = [1,2,3]".
The actual type is an array, but it is wrapped inside another object to make it readonly.
params. A relic from the original C. Very nicely modernized.
Will these functions be any less difficult to mock?
Hi Nick, thank you for the great content!
I'm having an issue with .net core applications running in a linux environment where gen2 memory isn't getting cleared by the GC. Seems to be a known issue, Any pointers?
I find that with the introduction of collection expressions the params keyword became much less useful as now you save only two characters: `MyMethod(param1, param2, param3)` with the params keyword vs `MyMethod([param1, param2, param3])` with Collection expressions.
Can you use this to loop through a list of objects returned by an async method. In order to amend some field values
would this help with logging and avoid us having to make overloads with multiple inputs (or libraries/generators that do this for us) ?
This video is impossible to share because i cant find it from the title when i want to send it to someone
Don't worry, it will get updated after the initial few days :)
@@nickchapsas glad to hear it! it's exciting to hear about all the new features explained as well as you do; just want to share it with everyone :)
I feel like if you're someone who gets excited about six nanosecond performance gains, you've picked the wrong programming language
Something you left out, params with a collection type, can still accept that collection as a single parameter.
Which is awesome because that should mean that spans can just be offered as a parameter as well as a set of values. :)
PARAMS ALL THE SPANS!!! :P
> all the values in a reference type will be boxed
no, value type instances are not boxed when you put them into an array
There's not much value in `params` anymore when you have the [] syntax at your disposal. Why writing `Method(params ReadOnlySpan values)` and call it as `Method(1, 2, 3)` when you can do `Method(ReadOnlySpan values)` and `Method([1, 2, 3])`?
Also I personally don't understand why the choice was made to require Add(T) method on the type. Why not a static FromSpan(ReadOnlySpan)? This approach loses statically available collection size info.
Yeah I misspoke here. I was thinking object arrays
@@nickchapsas I should've read other comments actually, sorry for bringing that up once more :)
I am pretty confused with the difference between Array and Enumerable.
I thought that for an arguments' set the compiler would have chosen the same background structure to pass the values.
It make sense to use IEnumerable since it's the base interface for all other types. Does this difference have anything to do with the way the set is read ?
According to the lowered code in SharpLab it does use an Array but it wraps it in a generated type that actually implements IEnumerable and whatnot
so you got some overhead from that and also calling methods on an interface is generally slower because of dynamic method binding/virtual function calls
The question on IEnumerable is that it is an iterator that have it's own special way of functioning, it firstly has to alloc an IEnumerator when iterating and then it need to call a bunch of methods each loop iteration to make it work. Arrays on the other hand are a special type in the CLR that is very optimized, so lowering it to the IEnumerable interface which is more generic will make things slower.
What I would ask is if using a generic param would work in this case, and if this would make it faster as JIT would have generic type information embedded in the method itself and would allow you to specify an IEnumerable as requirement (generic constraint) at the same time.
An array of integers doesn't box every integer though. It allocates a heap object because the array is a heap object. The array, as a heap object, requires 4 bytes of heap for every int + 4 bytes to store the array length, and idk if C# allocates more for other array metadata.
The integers are stored in the array, but they are not individually boxed. Instead it's one bigger box (the array) storing all of the integers. It's a single heap allocation.
Can a feature from latest version be introduced in previous versions?
What about the initial capacity of the collection?
In 12 years, I think I’ve only used params once. I haven’t been avoiding it for performance reasons.
Very well said, fancy things are not maintainable. I always use things that are common and most people know about it
Same here. It's great that they fixed it and that it can be fast, but I've never had any reason to use it.
I have params in every log method ever. Very useful to just dump all kinds of context into one method call
ReadOnlySpan has no Add function does it? I assume there are some very type specific optimisation around here. I just wonder how it performs on macs or linux...
Allowing Span significantly faster"? In what use cases, for a regular developer, is span 'significantly' faster than an array? int[] also does not have any boxing.
The worse performance of SumEnumerable is interesting in particular to me, because C# could theoretically internally just send an array as that inherits IEnumerable, which would be much more efficient even with boxing. This to me feels like it's not doing that and it's doing something else entirely.
And what if we use the span version and the caller calls it with many items? How to avoid stack overflows in that case?
There actually seems to be no limit I tested it with 32k items. Still uses an inline array
I don't think this is a problem in the everyday as variadics are not generally used for this.
Edit:
The Span/ReadOnlySpan struct contains only a reference and a 32 bit length, so it's no more than 16 bytes on the stack.
Original message:
It'll only stackalloc when you "use the params feature".. e.g.
var sum = Sum( 1,2,3,3,5,6 );
int Sum( params ReadOnlySpan values)
{
....
}
A Span can be 2^31 items when it's referencing items in an array.
var arr = new int[int.MaxValue];
var span = arr.AsSpan();
var sum = Sum( span );
So unless the caller puts an awful lot lot arguments in the function it won't overflow the stack. E.g.
var sum = Sum( 1,2,4, ..., 500_000);
Thinking about it a bit more, I'm pretty sure they'll check how many arguments are passed and heap allocate beyond a certain point.
Hey there, the feature looks nice however I have a small clarification:
This "In C# Arrays are reference types and all values of that array will be boxed" is a bit misleading.
th-cam.com/video/SlePiL1Azsg/w-d-xo.htmlsi=KQ0mIIXezzHSoo7j&t=165
It is true that in C# Arrays are reference types
And it is true that the array itself and all values are allocated on the heap.
However the values within this array are NOT boxed themself.
The boxing of the values themself only happen if you use an object[].
I think you are misinterpreting him, he can say all values are technically boxed since they are now in the heap instead of being in the stack (even if they are contiguous, because I don't think there is nothing inherent to the definition of boxing that requires it to be non-contiguous).
@@diadetediotedio6918 the array type is boxed, not the values so what OP said is correct.
@@metaltyphoon
No? The array type is not "boxed" unless you are interpreting any reference type as a boxed type (which is not standard in .NET).
@@diadetediotedio6918 yes that’s how I’m interpreting. Its the same as Rusts Box::. Boxed is a term for non stack allocated stuff. Every reference type in C# leaves on the heap and only the pointer to that “box” is on the stack
@@metaltyphoon
Ah, I got your reasoning, I know rust as well. But this terminology cannot be applied in C# in the same sense it is in Rust, because in Rust all types are stack-allocated by default until they are boxed (either with Box, Rc, Arc, etc), and in C# all classes are heap-allocated by default (so the term "boxing" would not make sense as their allocation behavior is intrinsic to their declaration and usage). Boxing in C# happens with structs (primitives and user defined types) and in this regard it works reasonably the "same" way as in Rust.
Often you need to pass already existed collection info params-accepting method, and so need to make an array from existed List or IEnumerable. And it always was like - what are you doing here. So even from that perspective it's already good.
Quite surprised with IEnumerable results. Perhaps array and list faster because of full array (and internal array of list) allocated as one flat chunk of memory, so cpu can predict code flow? Or it's because some 'context switching' between called and caller methods?
So what actually happens in the IEnumerable case? It has to be passed as something concrete like an array or List anyway, why is the performance so much worse?
it generates a wrapper around the array (internal sealed classz__ReadOnlyArray : IList, IReadOnlyList)
the reason why IEnumerable is so much worse against array/list is that the benchmark measures 2 things!! (collection creation time + sum)
if it only measured the time to create the collection, the performance would be somewhere between array and List
+the Sum method is optimized for T[] and List and can add several numbers at once thanks to SIMD, while z__ReadOnlyArray uses a non-optimized method that adds elements one at a time
Idk man. I’ve never felt limited by being restricted to array here.
Just a little comment when you said that Span passes the numbers by value. This is not entirely accurate, the Span itself is a value object, yes, but the values are passed by reference because it’s kind of like a pointer that references the integers in the stack in the calling method.
Yeah I was curious about that. What does he mean exactly? The span is allocated on the stack. So the pointer to the first memory location is on the stack? And that pointer gets passed by value, meaning copied, when passed as a parameter?
@@johnhershberg5915 The Span is a structure so it is on the stack and passed by value, he was right about that, but the data inside is not copied with the span, only a reference to it.
Span is the specific joke for c# for the joke of "how do you improve performance of your algorithm? with a hashmap"
hey Nick, I just want to tell you that the benchmark you're showing us is useless, it measures 2 things:
1. collection creation time
2. the duration of the Sum method
it doesn't sound like a problem but don't forget that the Sum method is optimized for array and List, so it gives biased results!!
(maybe instead of the Sum method you could just get the first element in the collection to simulate some work)
these results should not be biased:
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|----------------------------------------------------- |-----------------:|-----------------------:|------------------:|----------:|-----------------:|
| First_ReadOnlySpan | 0.0000 ns | 0.0000 ns | 0.0000 ns | - | - |
| First_Span | 2.0753 ns | 0.8027 ns | 0.0440 ns | - | - |
| First_Array | 6.7982 ns | 15.9521 ns | 0.8744 ns | 0.0153 | 64 B |
| First_Enumerable | 11.6156 ns | 1.5354 ns | 0.0842 ns | 0.0210 | 88 B |
| First_List | 16.9006 ns | 2.0089 ns | 0.1101 ns | 0.0229 | 96 B |
| First_NumbersCollectionBuilder | 25.1007 ns | 34.4873 ns | 1.8904 ns | 0.0287 | 120 B |
| First_NumbersAdd | 80.5004 ns | 255.2135 ns | 13.9891 ns | 0.0573 | 240 B |
1. Collection creation time is intentionally part of the benchmark. That’s the whole point
2. Using sum or a loop will produce the exact same results for all types in this benchmark
Hey Nick,
You switched to VS?
I am noticing that you are using VS for more of your videos than Rider.
Tbh I'm not sure how I feel about the duck typing an Add method to the type so it can be used with params keyword
Then you will be in horror to know that foreach and async/await is all about duck typing
@@metaltyphoon I know that but GetAwaiter and MoveNext look more specific
@@pagorbunov yeah but you cant have exceptions for one thing and not others. Beside there were proposals to allow general duck typing in C#. Mads talked about it in a podcast
@@metaltyphoon agree but the number of cases when you’re like what is going on will increase cause who knows what side effects that Add method could trigger
I thought it was going to be about the lock keyword. 😆
As a coder I feel like a Dinosaur staring up at the sky watching a meteor get larger and larger (AI). Anyone else?
Bro, why on earth would anybody want to make the most complex thing from the most simplest solution!!!
4:00 He's going to say Span isn't he?
4:04 Called it!
That was my first and immediate bet, too. 🙂
Damn, I thought the video will be about GOTO
This isn't even impressive nor amazing. Sure a decent QOL to expand target types params works with is fine enough. But it was already useful before. So much overhyping
You have to remember Nick's background industry is extremely perf conscious, so anything that saves CPU cycles (like no allocations) can make a huge overall perf and scalability improvements.
@@pilotboba where does he work at?
It's a second time they require to use Add method out of nowhere. Why don't use Interface to make it any sense?
"It can byte you"
Terrible transition to the GraphQL course upsell.
Awww so you actually use Visual Studio now
13 is now a lucky number?
Value types in array would not be boxed, you are wrong
nice!
Wow!
boah dude you are talking too much. come to the point!
I was hoping the removal of the GOTO.
C# is highly backward compatibility. There would have to be an extremely important reason to remove GOTO. Also, goto is used in several implementations in the BCL. So, yea, that's not going anywhere.
I want package function so much