Several looping constructs in the Enumerable class repeatedly set and/or test flags and counts for state changes that are only one-way. I first noticed this as a problem with .NET 3.5 when a bug in my code caused a sequence to repeat incorrectly and a call to Single(Func<TSource, bool> predicate) hanged rather than throwing. It would also have eventually thrown the wrong exception, though it would have taken an infeasible length of time to get that far. The cause was that Single was finding matches, counting how many were found, and then afterwards throwing if that value wasn't 1 rather than finding a match and then verifying there wasn't another.
There are several other cases where loops branch within each loop unnecessarily set or test the same variables repeatedly. There are others where a state that is checked for where they could short-circuit. For example, Min on floating-point values checks for IsNaN because it has to do so to ensure a total ordering, but then continues to loop despite provably having found the minimum value. Adding a check to short-circuit on IsNaN would optimise for the case of a sequence having a NaN value at a cost to those which don't, but here the check is already being made so there is no cost to short-circuiting. Conversely, in Max once a value has been found that is not NaN the continual check of IsNaN is needless.
While the changes in most of these cases are minor, they are by their very nature hit repeatedly, being used in loops in a very heavily used part of the framework. As such, such minor performance improvements are likely to pay off across a wide range of applications.
Several looping constructs in the
Enumerableclass repeatedly set and/or test flags and counts for state changes that are only one-way. I first noticed this as a problem with .NET 3.5 when a bug in my code caused a sequence to repeat incorrectly and a call toSingle(Func<TSource, bool> predicate)hanged rather than throwing. It would also have eventually thrown the wrong exception, though it would have taken an infeasible length of time to get that far. The cause was thatSinglewas finding matches, counting how many were found, and then afterwards throwing if that value wasn't1rather than finding a match and then verifying there wasn't another.There are several other cases where loops branch within each loop unnecessarily set or test the same variables repeatedly. There are others where a state that is checked for where they could short-circuit. For example,
Minon floating-point values checks forIsNaNbecause it has to do so to ensure a total ordering, but then continues to loop despite provably having found the minimum value. Adding a check to short-circuit onIsNaNwould optimise for the case of a sequence having aNaNvalue at a cost to those which don't, but here the check is already being made so there is no cost to short-circuiting. Conversely, inMaxonce a value has been found that is notNaNthe continual check ofIsNaNis needless.While the changes in most of these cases are minor, they are by their very nature hit repeatedly, being used in loops in a very heavily used part of the framework. As such, such minor performance improvements are likely to pay off across a wide range of applications.