1. 程式人生 > >C# 多執行緒 Parallel.ForEach 和 ForEach 效率問題研究及理解

C# 多執行緒 Parallel.ForEach 和 ForEach 效率問題研究及理解

      最近要做一個大資料dataTable迴圈操作,開始發現 運用foreach,進行大資料迴圈,並做了一些邏輯處理。在迴圈中耗費的時間過長。後來換成使用Parallel.ForEach來進行迴圈。 一開始認為, 資料比較大時,Parallel.ForEach肯定比 ForEach效率高,後來發現,其實並不是這樣。

我用了1000萬次迴圈測試:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
         
            Stopwatch Watch1 = new Stopwatch();
            Watch1.Start();
            List<entityA> source = new List<entityA>();
            for (int i = 0; i < 10000000; i++)
            {
                source.Add(new entityA
                {
                    name = "悟空" + i,
                    sex = i % 2 == 0 ? "男" : "女",
                    age = i
                });
            }
            Watch1.Stop();

            Console.WriteLine("list迴圈插入耗時:" + Watch1.ElapsedMilliseconds);
            Stopwatch Watch2 = new Stopwatch();
            Watch2.Start();
            loop1(source);
            Watch2.Stop();
            Console.WriteLine("一般for迴圈耗時:" + Watch2.ElapsedMilliseconds);

            Stopwatch Watch3 = new Stopwatch();
            Watch3.Start();
            loop2(source);
            Watch3.Stop();
            Console.WriteLine("一般foreach迴圈耗時:" + Watch3.ElapsedMilliseconds);


            Stopwatch Watch4 = new Stopwatch();
            Watch4.Start();
            loop3(source);
            Watch4.Stop();
            Console.WriteLine("並行for迴圈耗時:" + Watch4.ElapsedMilliseconds);

            Stopwatch Watch5 = new Stopwatch();
            Watch5.Start();
            loop4(source);
            Watch5.Stop();
            Console.WriteLine("並行foreach迴圈耗時:" + Watch5.ElapsedMilliseconds);
            Console.ReadLine();
        }
        //普通的for迴圈
        static void loop1(List<entityA> source)
        {
            int count = source.Count();
            for (int i = 0; i < count; i++)
            {
                source[0].age= + 10;
                //System.Threading.Thread.Sleep(10);
            }
        }

        //普通的foreach迴圈
        static void loop2(List<entityA> source)
        {
            foreach (entityA item in source)
            {
                item.age =+ 10;
                //System.Threading.Thread.Sleep(10);
            }
        }

        //並行的for迴圈
        static void loop3(List<entityA> source)
        {
            int count = source.Count();
            Parallel.For(0, count, item =>
            {
                //source[count].age= source[count].age + 10;
                //System.Threading.Thread.Sleep(10);
            });
        }

        //並行的foreach迴圈
        static void loop4(List<entityA> source)
        {
            Parallel.ForEach(source, item =>
            {
                item.age = item.age + 10;
                //System.Threading.Thread.Sleep(10);
            });
        }
    }


    //簡單的實體
    class entityA
    {
        public string name { set; get; }
        public string sex { set; get; }
        public int age { set; get; }
    }
}

執行結果:


結果居然是並行比一般的迴圈還耗時,但這是為什麼呢?

這是因為迴圈體內執行的任務開銷太小,僅僅是age+10 而已。微軟的文章已經指出任務的開銷大小對並行任務的影響。如果任務很小,那麼由於並行管理的附加開銷(任務分配,排程,同步等成本),可能並行執行並不是優化方案。這也是上述程式Foreach與For效率高出的原因。

基於這一點,我們對程式進行調整,迴圈1000次,每次裡面執行緒sleep(10),這樣我們試試。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
         
            Stopwatch Watch1 = new Stopwatch();
            Watch1.Start();
            List<entityA> source = new List<entityA>();
            for (int i = 0; i < 1000; i++)
            {
                source.Add(new entityA
                {
                    name = "悟空" + i,
                    sex = i % 2 == 0 ? "男" : "女",
                    age = i
                });
            }
            Watch1.Stop();

            Console.WriteLine("list迴圈插入耗時:" + Watch1.ElapsedMilliseconds);
            Stopwatch Watch2 = new Stopwatch();
            Watch2.Start();
            loop1(source);
            Watch2.Stop();
            Console.WriteLine("一般for迴圈耗時:" + Watch2.ElapsedMilliseconds);

            Stopwatch Watch3 = new Stopwatch();
            Watch3.Start();
            loop2(source);
            Watch3.Stop();
            Console.WriteLine("一般foreach迴圈耗時:" + Watch3.ElapsedMilliseconds);


            Stopwatch Watch4 = new Stopwatch();
            Watch4.Start();
            loop3(source);
            Watch4.Stop();
            Console.WriteLine("並行for迴圈耗時:" + Watch4.ElapsedMilliseconds);

            Stopwatch Watch5 = new Stopwatch();
            Watch5.Start();
            loop4(source);
            Watch5.Stop();
            Console.WriteLine("並行foreach迴圈耗時:" + Watch5.ElapsedMilliseconds);
            Console.ReadLine();
        }
        //普通的for迴圈
        static void loop1(List<entityA> source)
        {
            int count = source.Count();
            for (int i = 0; i < count; i++)
            {
                source[0].age= + 10;
                System.Threading.Thread.Sleep(10);
            }
        }

        //普通的foreach迴圈
        static void loop2(List<entityA> source)
        {
            foreach (entityA item in source)
            {
                item.age =+ 10;
                System.Threading.Thread.Sleep(10);
            }
        }

        //並行的for迴圈
        static void loop3(List<entityA> source)
        {
            int count = source.Count();
            Parallel.For(0, count, item =>
            {
                //source[count].age= source[count].age + 10;
                System.Threading.Thread.Sleep(10);
            });
        }

        //並行的foreach迴圈
        static void loop4(List<entityA> source)
        {
            Parallel.ForEach(source, item =>
            {
                item.age = item.age + 10;
                System.Threading.Thread.Sleep(10);
            });
        }
    }


    //簡單的實體
    class entityA
    {
        public string name { set; get; }
        public string sex { set; get; }
        public int age { set; get; }
    }
}

執行結果:


效率一目瞭然。

這樣的結果認證了我們上面的結論。當我們在迴圈中執行時間過長時,我們需要採用並行迴圈,效率較高。當時間過短,我們需要用foreach和for.