Linq中Count()和Any()引發的效率問題


1、count和any

今天看了鶴沖天的文章:Linq:切勿使用 Count() > 0 來判斷集合非空   有所收獲,寫下文章總結一下:

先看如下代碼:

 1        static void Main(string[] args)
 2         {
 3             Stopwatch watch = new Stopwatch();
 4             watch.Start();
 5             var nums = GetNums(0, 100000000);
 6             SomeAction(nums);
 7             watch.Stop();
 8             Console.WriteLine(watch.ElapsedMilliseconds);
 9             Console.WriteLine("over");
10             Console.ReadKey();
11         }
12         public static IEnumerable<int> GetNums(int start, int count)
13         {
14             var end = start + count;
15             for (int i = start; i < end; i++)
16                 yield return i;
17         }
18         public static void SomeAction<T>(IEnumerable<T> source)
19         {
20             //if (source.Count() > 0)  //2233 毫秒
21             if (source.Any())          //3 毫秒
22             {
23                 Console.WriteLine("have data");
24             }
25         }

自己調試了下,當yield循環低於100000次的話,count()和Any()的執行時間差不多,count()時間略大於any()所耗費的時間,當大於100000次的話,count()執行時間就遠比Any()執行時間多很多!例如上面的程序執行結果,count耗時2233毫秒,any卻只耗時3毫秒!這是為什么呢?我們來看下原因:分別反編譯 Enumerate.Count 的源碼和 Enumerate.Any的源碼,如下:

Enumerate.Any 實現代碼如下:

 1 public static bool Any<TSource>(this IEnumerable<TSource> source)
 2 {
 3     if (source == null)
 4     {
 5         throw Error.ArgumentNull("source");
 6     }
 7     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
 8     {
 9         if (enumerator.MoveNext())
10         {
11             return true;
12         }
13     }
14     return false;
15 }

 Enumerate.Count 的源碼如下:

 1 public static int Count<TSource>(this IEnumerable<TSource> source)
 2 {
 3     if (source == null)
 4     {
 5         throw Error.ArgumentNull("source");
 6     }
 7     ICollection<TSource> is2 = source as ICollection<TSource>;
 8     if (is2 != null)
 9     {
10         return is2.Count;
11     }
12     ICollection is3 = source as ICollection;
13     if (is3 != null)
14     {
15         return is3.Count;
16     }
17     int num = 0;
18     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
19     {
20         while (enumerator.MoveNext())
21         {
22             num++;
23         }
24     }
25     return num;
26 }

無論循環多少次,Any方法執行的時間總是最少的,大概10毫秒之內,我想大家從Any的源碼可以知道這個原因,Any方法先判斷集合是否為空,然后判斷集合是否有數據,只進行一次movenext(),並未進行循環查詢集合數量!所以耗費的時間當然一直是最少的了!

我們再看Count方法,同樣是先進行集合判空操作,然后判斷當前的集合source是否能轉成ICollection類型,如果能轉成,就直接返回此集合的Count屬性,不會在進行下面的循環獲取集合個數的操作了,為什么Count()方法比Any()方法執行的時間長呢?答案就在於此:因為GetNums方法中用yield返回純粹的IEnumerable<int>類型,但是ICollection<T>是繼承自IEnumerable<T>類型的,所以必然不好被轉換,也就是Count()源碼中的下面代碼不會執行

     ICollection is3 = source as ICollection;
     if (is3 != null)
     {
         return is3.Count;
     }

上面的代碼不會執行,那么必然會進行下面的代碼操作,也就是循環獲取集合的個數:

     int num = 0;
     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
     {
         while (enumerator.MoveNext())
         {
             num++;
         }
     }
     return num;

這樣必然會導致Count()方法耗時很久!

2、如果我們把GetNums進行如下修改,其他代碼不動:

       public static IEnumerable<int> GetNums(int start, int count)
        {
            List<int> list = new List<int>();
            var end = start + count;
            for (int i = start; i < end; i++)
                list.Add(i);
            return list;
        }

這時我們在執行程序,可以看到無論循環次數多少,Count和Any兩者執行的時間差不多,大概是1315毫秒左右,其中1312毫秒用於list集合的裝填工作,3毫秒用於Count或Any的判斷時間,為什么會出現這種情況呢,我想通過上面大家都知道了,因為此時GetNums方法返回的是List<T>類型,此類型繼承於ICollection,所以必然可以被轉換,就會返回此類型的Count屬性,Count 方法對 ICollection 進行了優化,直接訪返回它的 Count 屬性,也就是返回一個數,當然很快了,下面的循環獲取集合的個數當然也就不會執行了,也就節約了時間。

3、對某人問題的思考

有人說:”我有個程序里經常要判集合里是否僅有一個元素,又不能用Count,不得已自己寫了個擴展方法 bool Check(this IEnumerable<T> source, int n), 當讀到第n+1個元素就直接返回false“

我想如果此人用的集合是繼承於ICollection<T>的話,例如List<T>,那么是沒必要對IEnumerable<T>進行擴展的,通過反編譯Count源碼可以知道,如果集合source可以轉換成ICollection的話,是可以直接通過count屬性獲取到集合的總數量的,所以耗費的時間要不了多少,也就沒必要對IEnumerable進行擴展了!反之,如果不是繼承ICollection<T>的話,例如IEnumerable,就必須自己擴展IEnumerable方法了,如果還用count的話,就會造成循環了,也就降低了程序的運行效率了!

 4、參考

   http://www.cnblogs.com/ldp615/archive/2011/12/11/2284154.html#comment_tip  鶴沖天

   

作者:MrZivChu

2013-12-13   21:30:12


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM