亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

並列化は甘くないね。

今更ながら、Project Eulerでもやってみるべえか、と第一問目に手を出しました。

Problem 1
05 October 2001

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.

Find the sum of all the multiples of 3 or 5 below 1000.

さすがにこれは簡単。

        static void Main(string[] args)
        {
            var res1 = GetEnumNum(1000).Where((count) => count % 3 == 0 || count % 5 == 0).Sum();
            Console.WriteLine(res1);
        }

        static IEnumerable<int> GetEnumNum(int i)
        {
            for (int j = 0; j <= i; j++)
            { yield return j; }            
        }

...ある数までの列挙をする関数ってありましたっけ・・・?もっとスマートに書ける???
追記(2010/08/02)
つ 【Enumable.Range】。馬鹿すぎる・・・orz
(参考:d:id:SuZu:20100731:1280577571

まあ、それはともかく。せっかくLINQ使ったので並列化して高速化してみるべえ、と。以下のように書き換え

        static void Main(string[] args)
        {
            var sw = new Stopwatch();
            sw.Start();
            var res1 = GetEnumNum(1000).AsParallel().Where((count) => count % 3 == 0 || count % 5 == 0).Sum();
            sw.Stop();
            Console.WriteLine("PLinq: " + sw.ElapsedTicks + "\t" + res1);

            sw.Reset();
            sw.Start();
            var res2  = GetEnumNum(1000).Where((count) => count % 3 == 0 || count % 5 == 0).Sum();
            sw.Stop();
            Console.WriteLine("Linq: " + sw.ElapsedTicks + "\t" + res2);


            Console.Read();
        }

        static IEnumerable<int> GetEnumNum(int i)
        {
            for (int j = 0; j <= i; j++)
            { yield return j; }            
        }

結果(CPUはPhenomX4です):

  • PLinq: 65519 234168
  • Linq: 11554 234168


・・・あれ??PLINQおせえ!!!
とか思いましたが、MSDNに普通にありましたとも。

クエリは、多くの場合並列化できますが、並列クエリの設定に伴うオーバーヘッドは、並列化によって得られるパフォーマンスの利点よりも大きくなります。クエリが大量の計算を実行しない場合、またはデータ ソースが小さい場合、PLINQ クエリは、順次的な LINQ to Objects クエリよりも低速になります。


PLINQ の概要

intだと桁あふれするのでSumでdouble型への変換を行って数を増やすと要素100万でPLinqが逆転しますた。まあWhere句とかにもっと重い処理を入れると変わるんでしょうが・・・

安易に並列化すればいいってもんでもないのは耳学問では知ってたけど、実感できてよかた。