.NET Framework4.0のPLINQを試す

はじめに

最近.NETネタが多いのですが、.NET Framework4.0から実装されたPLINQを試してみました。
PLINQについてはこのあたりの記事を見ていただければだいたい分かると思います。LINQをマルチコアプロセッサで動かして処理を向上させる機能ですね。
わざわざスレッドを作成しなくてもお手軽にマルチコア対応のプログラミングができるワケです。

早速VisualStudio2010のBeta版でテストコードを書いてみました。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Cursor = Cursors.WaitCursor;

            try
            {
                Stopwatch sw = new Stopwatch();
                Random rand = new Random();
                List<Data> list = new List<Data>();
                int Count = 0;
                for (int id = 0; id < 10000; id++)
                {
                    Data data = new Data();
                    data.ID = id;
                    data.Value = rand.Next(10000);
                    list.Add(data);
                }
                sw.Start();
                Count = (from record in list.AsParallel()
                         where record.Value % 2 == 0
                         select record).Count();
                sw.Stop();
                TimeSpan ts = sw.Elapsed;
                textBox1.Text = ts.ToString();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                this.Cursor = Cursors.Arrow;
            }
        }
    }

    public class Data
    {
        private int m_id;
        private int m_value;

        public int ID
        {
            set{m_id = value;}
            get { return m_id; }
        }

        public int Value
        {
            set { m_value = value; }
            get 
            {
                System.Threading.Thread.Sleep(1);
                return m_value; 
            }
        }
    }
}

実行

PLINQの実装方法は超簡単で対象データに.AsParallel()をつけるだけ。早速実行してみます。

ほうほう。5秒くらいね。
次は.AsParallel()を取り除いて普通のLINQで試すと...

10秒とな!うちのPCのCPUがCore2DuoE6700なのできっちり2倍の速度が出ています。すごい。

例外処理

「では、VisualStudio2010が発売されたら既存プログラムのLINQをかたっぱしからPLINQに変更してやろう」と思っている方はちょっとお待ちを。
マルチスレッドのプログラムを組んだことがある方ならご理解いただけるでしょうが、サブスレッド中の例外をメインのスレッドに伝えてやるにはdelegate等を使用しなくてはならずちょっと面倒です。
試しに、Value値を取得する際に時々例外を発生するようにプログラムを一部変更してみましょう。

        public int Value
        {
            set { m_value = value; }
            get 
            {
                System.Threading.Thread.Sleep(1);
                if (m_value < 500)
                {
                    throw new Exception("Exception!");
                }
                return m_value; 
            }
        }

実行すると

というように、エラーを拾う事ができません。型付DataSet内のDataTableを検索中にint項目にnull値がセットされてて例外が発生することとかよくあるとおもうんですが、このままではマズイですね。
そこで、PLINQ中で発生した例外を取得する方法ですが、AggregateExceptionを使用します。
これでcatchしてやるとPLINQ中で発生した例外を配列で取得することができます。

            try
            {
                Stopwatch sw = new Stopwatch();
                Random rand = new Random();
                List<Data> list = new List<Data>();
                int Count = 0;
                for (int id = 0; id < 10000; id++)
                {
                    Data data = new Data();
                    data.ID = id;
                    data.Value = rand.Next(10000);
                    list.Add(data);
                }
                sw.Start();
                Count = (from record in list.AsParallel()
                         where record.Value % 2 == 0
                         select record).Count();
                sw.Stop();
                TimeSpan ts = sw.Elapsed;
                textBox1.Text = ts.ToString();
            }
            catch (AggregateException ex)
            {
                foreach (Exception exception in ex.InnerExceptions)
                {
                    MessageBox.Show(exception.Message);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                this.Cursor = Cursors.Arrow;
            }

上の例ではex.InnerExceptionsの数だけエラーメッセージが出てきてうっとうしいので、実際は何か気の利いた処理をしてください。

まとめ

PLINQはLINQを総とっかえできるようなものではありません。例外処理や遅延実行されることも考えながら「ここぞ!」という箇所で使うのが良さそうです。
んでも、この速さは魅力ですね。積極的に使えるようになにかいい方法を考えよう。