Shi*_*raj 2 parallel-processing parallel-extensions plinq task-parallel-library
为什么PLINQ输出与顺序处理和Parallel.For循环不同
我想添加10,000,000个数字的平方根.以下是3个案例的代码:
顺序循环:
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 loop之间有区别?
我强烈怀疑这是因为算术与双打不是真正的联想.在对值进行求和时可能会丢失信息,并且确切地丢失哪些信息将取决于操作的顺序.
这是一个显示效果的示例:
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时仍然相关.
在第二种情况下,将0.00000000000000001添加到1始终只会导致1,因为双精度中没有足够的信息来表示1.00000000000000001 - 所以最终结果仍然只有1.
编辑:我想到了另一个可能令人困惑的方面.对于局部变量,JIT编译器能够(并允许)使用80位FP寄存器,这意味着可以在较少信息丢失的情况下执行算术运算.这不是实例变量,这肯定必须是64位的情况.在Parallel.For情况下,total变量实际上是生成的类中的实例变量,因为它是由lambda表达式捕获的.这可能会改变结果 - 但它很可能取决于计算机体系结构,CLR版本等.