2

PLINQ 出力が順次処理や Parallel.For ループと異なる理由

10,000,000 個の数値の平方根の合計を追加したい.. 3 つのケースのコードは次のとおりです。

シーケンシャル for ループ:

double sum = 0.0;
for(int i = 1;i<10000001;i++)
sum += Math.Sqrt(i);

これの出力は次のとおりです: 21081852648.717

Parallel.For ループを使用するようになりました:

object locker = new object();
double total ;

Parallel.For(1,10000001,
()=>0.0,
(i,state,local)=> local+Math.Sqrt(i),
(local)=>
{
  lock(locker){ total += local; }
}
);

これの出力は次のとおりです: 21081852648.7199

PLINQ を使用中

double tot =  ParallelEnumerable.Range(1, 10000000)
                .Sum(i => Math.Sqrt(i)); 

これの出力は次のとおりです: 21081852648.72

PLINQ 出力と Parallel.For および Sequential for ループに違いがあるのはなぜですか?

4

1 に答える 1

5

double を使用した算術演算は真に結合的ではないためだと強く思います。値を合計する際に情報が失われる可能性があり、正確にどの情報が失われるかは操作の順序によって異なります。

その効果を示す例を次に示します。

using System;

class Test
{
    static void Main()
    {
        double d1 = 0d;
        for (int i = 0; i < 10000; i++)
        {
            d1 += 0.00000000000000001;
        }
        d1 += 1;
        Console.WriteLine(d1);

        double d2 = 1d;
        for (int i = 0; i < 10000; i++)
        {
            d2 += 0.00000000000000001;
        }
        Console.WriteLine(d2);
    }
}

最初のケースでは、非常に小さな数を何度も追加して、1 に追加しても関連性が保たれるほど十分に大きくなることができます。

2 番目のケースでは、0.00000000000000001 を 1 に加算すると、double には 1.00000000000000001 を表すのに十分な情報がないため、常に 1 になります。したがって、最終結果は 1 のままです。

編集:混乱を招く可能性のある別の側面について考えました。ローカル変数の場合、JIT コンパイラーは 80 ビットの FP レジスターを使用できます (そして使用できます)。これは、情報の損失を抑えて演算を実行できることを意味します。これは、64 ビットでなければならないインスタンス変数には当てはまりません。Parallel.For の場合、total変数はラムダ式によってキャプチャされるため、実際には生成されたクラスのインスタンス変数になります。これにより結果変わる可能性がありますが、コンピューターのアーキテクチャ、CLR のバージョンなどに依存する可能性があります。

于 2011-02-18T07:32:21.973 に答える