C#におけるforeach、LINQ、デリゲートによるコレクション検索のパフォーマンス比較

前日にLINQのパフォーマンスについて議論した際、自分はLINQが性能劣化していると考えていたが、実際にはLINQも特定の面では優れた性能を発揮するものであることを学んだ。ただし、LINQ to SQLについては別として、以下に簡単なパフォーマンステストのコードを示す。このテストではDateTimeではなくStopwatchを使用して計測している点に注意が必要である。初心者の私は最初はDateTimeを使っていて、LINQのパフォーマンスが悪いと思っていたが、Stopwatchを使用することで真実に気づいた。LINQに対する偏見を持っている方々は、LINQを理解し直す価値があるかもしれない。もし誤解があれば、皆様のご指摘を歓迎する。

    class Program<br></br>    {<br></br>        static void Main(string[] args)<br></br>        {<br></br>            test();<br></br>        }<br></br><br></br>       static void test()<br></br>        {<br></br>            List<DataItem> items = new List<DataItem>();<br></br>            for (int i = 0; i < 10000000; i++)<br></br>            {<br></br>                DataItem data=new DataItem();<br></br>                data.Label = "テストデータ" + i;<br></br>                data.Index = i;<br></br>                items.Add(data);<br></br>            }<br></br><br></br>            Stopwatch watch = new Stopwatch();<br></br><br></br><br></br>            #region forループ<br></br>            watch.Start();<br></br>            List<DataItem> result1 = new List<DataItem>();<br></br><br></br>            foreach (DataItem item in items)<br></br>            {<br></br>                if (item.Index >= 52 && item.Index < 850) { result1.Add(item); }<br></br>            }<br></br>            watch.Stop();<br></br><br></br>            Console.Write("一致件数" + result1.Count + "、forループ時間:");<br></br>            Console.WriteLine(watch.Elapsed.Ticks);<br></br>            #endregion<br></br><br></br><br></br>            #region LINQ<br></br>            watch = new Stopwatch();<br></br>            watch.Start();<br></br>            var result2 = items.Where(product => product.Index >= 52 && product.Index < 850);<br></br>            watch.Stop();<br></br><br></br><br></br>            Console.Write("一致件数" + result2.Count() + "、LINQ時間:");<br></br>            Console.WriteLine(watch.Elapsed.Ticks);<br></br>            #endregion<br></br><br></br><br></br>            #region デリゲート<br></br>            watch = new Stopwatch();<br></br>            watch.Start();<br></br>            List<DataItem> result3 = items.FindAll(delegate(DataItem item)<br></br>            {<br></br>                return item.Index >= 52 && item.Index < 850;<br></br>            });<br></br>            watch.Stop();<br></br><br></br>            Console.Write("一致件数" + result3.Count() + "、デリゲート時間:");<br></br>            Console.WriteLine(watch.Elapsed.Ticks);<br></br>            #endregion<br></br><br></br><br></br>            Console.Read();  <br></br>        }<br></br><br></br>        public class DataItem<br></br>        {<br></br>            public string Label { get; set; }<br></br>            public int Index { get; set; }<br></br>        }<br></br>    }

テスト結果のスクリーンショットは以下の通り:

誤解がある場合はご指摘いただきたい。

園内のコメントをもとにコードを再構成した。シンプルな内容でも、間違いを指摘してもらうことは成長にとって重要であり、皆様の丁寧な指摘に感謝する。

再構成後のコードは以下の通り:

View Code ``` class Program { static void Main(string[] args) { test(); } static void test() { List<DataItem> items = new List<DataItem>(); for (int i = 0; i < 10000000; i++) { DataItem data=new DataItem(); data.Label = "テストデータ" + i; data.Index = i; items.Add(data); } Stopwatch watch = new Stopwatch(); #region forループ watch.Start(); List<DataItem> result1 = new List<DataItem>(); int matchedCount = 0; foreach (DataItem item in items) { if (item.Index >= 52 && item.Index < 850) { result1.Add(item); } } matchedCount = result1.Count; watch.Stop(); Console.Write("一致件数" + matchedCount + "、forループ時間:"); Console.WriteLine(watch.Elapsed.Ticks); #endregion #region LINQ watch = new Stopwatch(); watch.Start(); var result2 = items.Where(product => product.Index >= 52 && product.Index < 850); //items.Where(product => product.Index >= 52 && product.Index < 850).ToArray().Count(); int count2 = result2.Count(); watch.Stop(); Console.Write("一致件数" + count2 + "、LINQ時間:"); Console.WriteLine(watch.Elapsed.Ticks); #endregion #region デリゲート watch = new Stopwatch(); watch.Start(); List<DataItem> result3 = items.FindAll(delegate(DataItem item) { return item.Index >= 52 && item.Index < 850; }); int count3 = result3.Count(); watch.Stop(); Console.Write("一致件数" + count3 + "、デリゲート時間:"); Console.WriteLine(watch.Elapsed.Ticks); #endregion Console.Read(); } public class DataItem { public string Label { get; set; } public int Index { get; set; } } }


テスト結果

第3回目のコード更新では順序の影響を排除した:

View Code ```
    class Program<br></br>    {<br></br>       static void Main(string[] args)<br></br>        {<br></br>            List<DataItem> items = new List<DataItem>();<br></br>            for (int i = 0; i < 10000000; i++)<br></br>            {<br></br>                DataItem data = new DataItem();<br></br>                data.Label = "テストデータ" + i;<br></br>                data.Index = i;<br></br>                items.Add(data);<br></br>            }<br></br><br></br>            test_linq(items);<br></br><br></br>            test_foreach(items);<br></br><br></br>            test_delegate(items);<br></br><br></br>           <br></br>            Console.Read();<br></br>        }<br></br><br></br>       static void test_foreach(List<DataItem> items)<br></br>        {<br></br>            Stopwatch watch = new Stopwatch();<br></br>            #region foreachループ<br></br>            watch.Start();<br></br>            List<DataItem> result1 = new List<DataItem>();<br></br><br></br>            int matchedCount = 0;<br></br>            foreach (DataItem item in items)<br></br>            {<br></br>                if (item.Index >= 52 && item.Index < 850) { result1.Add(item); }<br></br>            }<br></br>            matchedCount = result1.Count;<br></br>            watch.Stop();<br></br><br></br>            Console.Write("一致件数" + matchedCount + "、foreachループ時間:");<br></br>            Console.WriteLine(watch.Elapsed.Ticks);<br></br>            #endregion<br></br>        }<br></br>       static void test_linq(List<DataItem> items)<br></br>        {<br></br>            Stopwatch watch = new Stopwatch();<br></br>            #region LINQ<br></br>            watch = new Stopwatch();<br></br>            watch.Start();<br></br>            var result2 = items.Where(product => product.Index >= 52 && product.Index < 850);<br></br>            //items.Where(product => product.Index >= 52 && product.Index < 850).ToArray().Count();<br></br>            int count2 = result2.Count();<br></br>            watch.Stop();<br></br><br></br><br></br>            Console.Write("一致件数" + count2 + "、LINQ時間:");<br></br>            Console.WriteLine(watch.Elapsed.Ticks);<br></br>            #endregion<br></br>        }<br></br>       static void test_delegate(List<DataItem> items)<br></br>        {<br></br>            Stopwatch watch = new Stopwatch();<br></br>            #region デリゲート<br></br>            watch = new Stopwatch();<br></br>            watch.Start();<br></br>            List<DataItem> result3 = items.FindAll(delegate(DataItem item)<br></br>            {<br></br>                return item.Index >= 52 && item.Index < 850;<br></br>            });<br></br>            int count3 = result3.Count();<br></br>            watch.Stop();<br></br><br></br>            Console.Write("一致件数" + count3 + "、デリゲート時間:");<br></br>            Console.WriteLine(watch.Elapsed.Ticks);<br></br>            #endregion<br></br>        }<br></br><br></br>        public class DataItem<br></br>        {<br></br>            public string Label { get; set; }<br></br>            public int Index { get; set; }<br></br>        }<br></br>    }

上記のスクリーンショットを見ると、大きな差異はほとんどない。

結論:LINQの遅延評価機能を理解していなかったため、Count操作がLINQの実際のクエリ実行を引き起こしていた。まだ深く理解していないが、いずれ理解できるだろう。

ブログアドレス: http://www.jqpress.com/post/43.aspx

タグ: C# LINQ foreach delegate パフォーマンス

5月18日 05:00 投稿