Deferred execution and eager evaluation
你能给我一个例子来说明C中的延迟执行和热切的评估吗?
我从msdn中了解到,在linq中延迟的执行可以通过懒惰的或热切的评估来实现。我可以在Internet上找到延迟执行的例子,但我找不到任何延迟执行的例子,这些例子都是通过延迟执行进行的。
此外,延迟执行与延迟评估有什么不同?在我看来,两者看起来是一样的。你也能举个例子吗?
下面是我的答案,但也请注意,乔恩·斯基特今天在他的博客《关于一个事实》中谈到了这一点,即他对"懒惰"的msdn含义并不完全满意,因为msdn并不清楚"懒惰"到底是什么意思,当他们用它来表示你有多懒惰时?他的文章很有趣。
另外,wikipedia认为应该为懒惰的评估维护三个规则,并且在msdn意义上不尊重第三点,因为如果再次调用
考虑一个函数
1 | int Computation(int index) |
立即执行
1 2 3 4 5 6 7 8 9 | IEnumerable<int> GetComputation(int maxIndex) { var result = new int[maxIndex]; for(int i = 0; i < maxIndex; i++) { result[i] = Computation(i); } return result; } |
- 当函数被称为
Computation 时,执行maxIndex 次。 GetEnumerator 返回枚举器的一个新实例,不再执行任何操作。- 每次调用
MoveNext 时,都会将存储在IEnumerator 的Current 成员的下一个数组单元中的值放入,仅此而已。
成本:前期大,枚举时小(仅一份副本)
延期但急于执行1 2 3 4 5 6 7 8 9 10 11 12 | IEnumerable<int> GetComputation(int maxIndex) { var result = new int[maxIndex]; for(int i = 0; i < maxIndex; i++) { result[i] = Computation(i); } foreach(var value in result) { yield return value; } } |
- 当调用函数时,将创建实现
IEnumerable 的自动生成类(在规范中称为"可枚举对象")的实例,并在其中存储参数(maxIndex 的副本。 GetEnumerator 返回枚举器的一个新实例,不再执行任何操作。- 对
MoveNext 的第一个调用执行的最大值乘以计算方法,将结果存储在数组中,Current 将返回第一个值。 - 对
MoveNext 的每个后续调用都会将存储在数组中的值放入Current 。
成本:前期无,枚举开始时大,枚举过程中小(只有一个副本)
拖延和懒惰的执行1 2 3 4 5 6 7 | IEnumerable<int> GetComputation(int maxIndex) { for(int i = 0; i < maxIndex; i++) { yield return Computation(i); } } |
- 当调用函数时,会发生与延迟执行相同的情况。
GetEnumerator 返回枚举器的一个新实例,不再执行任何操作。- 每次调用
MoveNext 执行一次Computation 代码,将值放入Current 中,让调用者立即对结果采取行动。
大多数LINQ使用延迟和延迟执行,但有些函数不能像排序那样。
成本:不预先计算,在枚举期间中等(计算在那里执行)
总结- immediate是指计算/执行在函数中完成,并在函数返回后完成。(与大多数C代码一样,完全渴望评估)
- 延迟/热切意味着大部分工作将在第一个
MoveNext 或创建IEnumerator 实例时(对于IEnumerable ,调用GetEnumerator )完成。 - 延迟/延迟意味着每次调用
MoveNext 时都将完成工作,但之前什么都没有。
Parallel Linq做的有点不同,因为从调用方的角度来看,计算可以被认为是延迟/延迟的,但在内部,一些元素的计算在枚举开始后立即并行开始。结果是,如果下一个值已经存在,您将立即得到它,否则您将不得不等待它。
您可以热切地评估延迟执行IEnumerable的一种方法是使用linq的.to array()函数将其转换为数组。
1 | var evaluated = enumerable.ToArray(); |
这将强制计算完全可枚举的值,然后您就可以使用数组来执行任何操作。