public static class LinqDiagnosticExtensions
{
/// <summary>
///
/// Measuring exection time of lazy sequences like those
/// returned by LINQ extensions is a bit more complicated
/// than some may think. First, there can be overhead in
/// both the constructor and the Dispose() method of the
/// IEnumerator<T> obtained from the object returned by
/// by LINQ methods or your own block iterators, that will
/// be included in the measurement resulting from timing
/// how long a call to Enumerable.Count() takes to return.
/// This may, or may not be the intent, and in the case of
/// your own LINQ iterators, you could be doing quite a bit
/// of work before and/or after the loop containing the
/// call to yield return.
///
/// Another problem is that simply timing how long the
/// Enumerable.Count() method takes to return does not
/// always measure how much work is required to obtain
/// every element in an IEnumerable<T>. It only tells
/// how long it takes to count the number of elements
/// that will be enumerated. The actual work required to
/// get each element includes both the work done by the
/// the IEnumerator<T>'s MoveNext() implementation, and
/// the implementation of the IEnumerator<T>'s Current
/// property 'getter' method. Enumerable.Count() does not
/// access the IEnumerator<T>'s Current property at all.
///
/// So, if we want to be precise, then we need to do a bit
/// more than simply call Enumerable.Count(). Here is a
/// very simple extension method that returns a TimeSpan
/// that measures the actual time required to obtain all
/// elements from an IEnumerable<T>, that will optionally
/// omit the overhead of the IEnumerator<T>'s constructor
/// and Dispose() implementations, along with the overhead
/// of the first call to MoveNext(), which is typically where
/// Linq iterators do 'very lazy' initialization.
///
/// </summary>
public static TimeSpan GetExecutionTime<T>( this IEnumerable<T> source, bool includeOverhead = true )
{
T last = default( T );
Stopwatch sw
= new Stopwatch
(); if( includeOverhead )
sw.Start();
using( var e = source.GetEnumerator() )
{
/// linq iterators are state machines that
/// typically do initialization in the first
/// call to MoveNext(), so we will include
/// that in the overhead rather than in the
/// raw iteration time:
bool flag = e.MoveNext();
if( !includeOverhead )
sw.Start();
// begin 'raw' iteration time:
while( flag )
{
/// Let's make sure that if get_Current() is
/// doing something significant, we measure
/// that as well:
last = e.Current;
flag = e.MoveNext();
}
// end of 'raw' iteration time:
if( !includeOverhead )
sw.Stop();
}
if( includeOverhead )
sw.Stop();
return sw.Elapsed;
}
}