foreach loop could behave like for loops for IList values #2586
Replies: 40 comments
-
This isn't always the case that for is faster than foreach. For example looping over an ImmutableList is O(n) when using |
Beta Was this translation helpful? Give feedback.
-
OK, I didn't do benchmark with ImmutableList, just my own use case. Is it possible to do this when the for loop is known to be faster. For my purpos, this is typically the case when the compilator should choose to change your code, without having to guess which is faster of for and foreach. This may be to complicated for common developpers. If this seems too complicated let's close it. |
Beta Was this translation helpful? Give feedback.
-
How would the compiler know which is faster? |
Beta Was this translation helpful? Give feedback.
-
I've take a look to ImmutableList. This is obviously not a good idea, to use for loop. (But without knowing, I'll may use the for loop). Maybe a #something before the class. This should be present on List and array. As they are lot used, this may be a good idea. Any way: this need to have to be able to have information read at compile time. I don't know if this can be done. If too complicated let's close this. But this seems to me great. |
Beta Was this translation helpful? Give feedback.
-
But the type isn't known at compile time. At compile time all we have is an IEnumerable |
Beta Was this translation helpful? Give feedback.
-
OK, so false good idea. |
Beta Was this translation helpful? Give feedback.
-
In you example it would go faster if you also only evaulated int count = valuesAsList.Count;
for (int position = 0; position < count; position++)
{
doOnValue?.Invoke(valuesAsList[position]);
} Sprinkle in some pattern matching to combine the cast and null check public static void ForEach<T>(this IEnumerable<T> values, Action<T> doOnValue)
{
if (values is IList<T> list)
{
int count = list.Count;
for (int position = 0; position < count; position++)
{
doOnValue?.Invoke(list[position]);
}
}
else
{
foreach (var value in values)
{
doOnValue?.Invoke(value);
}
}
} |
Beta Was this translation helpful? Give feedback.
-
yes, you're right. I'll fix it. |
Beta Was this translation helpful? Give feedback.
-
In Java they used a marker interface https://docs.oracle.com/javase/8/docs/api/java/util/RandomAccess.html |
Beta Was this translation helpful? Give feedback.
-
But ImmutableList does have efficient random access. It's just slower than sequential access. |
Beta Was this translation helpful? Give feedback.
-
So if it's slower, it just don't have the interface (could be rename in IsFasterWithRandomAccess). |
Beta Was this translation helpful? Give feedback.
-
Per the Java docs I linked the general rule of thumb is to apply that marker interface if it is expected that a |
Beta Was this translation helpful? Give feedback.
-
But you also have to evaluate whether the cost of checking for that interface is greater than the benefits it gets you. Note that Java doesn't change the behaviour of a foreach loop to check for this interface. |
Beta Was this translation helpful? Give feedback.
-
I know that Linq is doing lot of check optimization to check if there is a IList or just a IEnumerable. public static TSource First<TSource>(this IEnumerable<TSource> source) {
if (source == null) throw Error.ArgumentNull("source");
IList<TSource> list = source as IList<TSource>;
if (list != null) {
if (list.Count > 0) return list[0];
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return e.Current;
}
}
throw Error.NoElements();
} The question is: is the average performance boost better ? |
Beta Was this translation helpful? Give feedback.
-
Linq doesn't do those checks when it's just planning to enumerate. It only does the checks if it can use them more effectively in some way. For example it can make first allocation free by checking for IList. |
Beta Was this translation helpful? Give feedback.
-
This is a breaking change, and they are inherently different things public class OhNo<T> : IList<T>, IEnumerable<T> where T : new() // implicit
{
public IEnumerator<T> GetEnumerator() => null;
public T this[int index] => new T();
public int Count => int.MinValue;
} Evaluating this using |
Beta Was this translation helpful? Give feedback.
-
OK |
Beta Was this translation helpful? Give feedback.
-
Hello, I was wondering something, excuse-me if I post my idea, when you have already answer. Is it possible to make an optimisation on the Enumerable class, to prefer the for loop instead. Because the List and Array have a special Enumerator ( from [https://github.com/microsoft/referencesource/blob/master/mscorlib/system/collections/generic/list.cs](List repositoryl) ) [Serializable]
public struct Enumerator : IEnumerator<T>, System.Collections.IEnumerator
{
private List<T> list;
private int index;
private int version;
private T current;
internal Enumerator(List<T> list) {
this.list = list;
index = 0;
version = list._version;
current = default(T);
}
public void Dispose() {
}
public bool MoveNext() {
List<T> localList = list;
if (version == localList._version && ((uint)index < (uint)localList._size))
{
current = localList._items[index];
index++;
return true;
}
return MoveNextRare();
}
private bool MoveNextRare()
{
if (version != list._version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = list._size + 1;
current = default(T);
return false;
}
public T Current {
get {
return current;
}
}
Object System.Collections.IEnumerator.Current {
get {
if( index == 0 || index == list._size + 1) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return Current;
}
}
void System.Collections.IEnumerator.Reset() {
if (version != list._version) {
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
index = 0;
current = default(T);
}
}
}
} This would be nice to have the roslyn warning on the Enumerator type, with maybe an interface IEnumerablePreferForLoop. What do-you think ? |
Beta Was this translation helpful? Give feedback.
-
If that's what you want you can write an analyzer which warns on |
Beta Was this translation helpful? Give feedback.
-
Yes I'd like (maybe I would I like the CLR to optimize the loop with something like: But any way, this seems me much more easier to implement, than what we speek about. Since this is just an interface you put on Enumerator, and not on upper class. |
Beta Was this translation helpful? Give feedback.
-
Using a for loop would lose version-safety, i.e. throwing an exception if the collection was modified whilst enumerating. This is not something the compiler can just swap out for you. |
Beta Was this translation helpful? Give feedback.
-
OK so bad idea (once more). |
Beta Was this translation helpful? Give feedback.
-
It's not that it is a bad idea. It's just that there are a lot of subtleties to any language proposal and unless you have previous experience with that, it is very easy to miss the various reasons why something might not work. Everyone has pain points with every language and so everyone has ideas on how to fix those pain points. Putting up issues for those ideas is completely fine and the appropriate people (sometimes just other members from the community) will comment on why 'x' will or won't work, various edge cases that need to be considered, or link to existing/related proposals. If people don't log an issue for the pain points they have, they language team may never know that some people consider it a pain point and so it or some feature to address it, may never come into existence (as it then requires someone from the language team to consider it a pain point, which they may not). |
Beta Was this translation helpful? Give feedback.
-
@benaadams Isn't this a compiler optimizaton? int count = list.Count;
for (int position = 0; position < count; position++)
{
} |
Beta Was this translation helpful? Give feedback.
-
No, the compiler can't make that optimization. What the interface call You can make the choice to assume its well behaved and will return the same value each time so manually hoist it out of the loop; but neither compiler can make that assumption. If its a direct call rather than a interface, or virtual call (like on an array, span or List); then the Jit will likely inline it an then hoist it out of the loop automatically; but it cannot do that with an interface dispatch as it cannot "see" what the method does. |
Beta Was this translation helpful? Give feedback.
-
For a |
Beta Was this translation helpful? Give feedback.
-
Note: a smart linked list coudl keep track of the last node/index that was looked up to make striding like this fast. It's something i've seen in a lot of interesting datastructures (for example, string-apis with non-fixed-width-char lengths). |
Beta Was this translation helpful? Give feedback.
-
Sure, theoretically an implementation could, but the one in .NET doesn't and it's not wise to assume that any implementation would do that. And ultimately something like that would just be more complicated than just using an enumerator since now the list has to track mutable state which would need to be synchronized across threads, etc. |
Beta Was this translation helpful? Give feedback.
-
Sure :) Just throwing it out there for completeness :D |
Beta Was this translation helpful? Give feedback.
-
@benaadams Thanks, yeah runtime optimization is what I meant. I was thinking of C++ compiler optimizations and forgot that C# needed a run-time for a second there.
Doesn't it "see" after the first dispatch? Once the procedure that it invoked does it's task it must be able to infer and optimize following calls to it? @HaloFour wow, that's gonna give me a nightmare. xD |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hello,
This is the continuation of this issue
What is my problem
I get big performance increase when dealing with calculating code with lot of arrays, which such a method (I just check that the IEnumerable is IList, since this has been lot done in Linq code)
the foreach is a good way for code lisibility. And I have the advantage of for boost , with foreach lisibility.
What I would like
I'd like the foreach to behave like for loop, i.e not using IEnumerable, when dealing with IList.
Is it possible ?
Regards,
VGib
Beta Was this translation helpful? Give feedback.
All reactions