这是 PLINQ 错误吗?
为什么 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 循环之间存在差异?
Why PLINQ output is different than sequential processing and Parallel.For loop
I want to add sum of square root of 10,000,000 numbers.. Here is the code for 3 cases:
sequential for loop:
double sum = 0.0;
for(int i = 1;i<10000001;i++)
sum += Math.Sqrt(i);
Output of this is: 21081852648.717
Now Using Parallel.For loop:
object locker = new object();
double total ;
Parallel.For(1,10000001,
()=>0.0,
(i,state,local)=> local+Math.Sqrt(i),
(local)=>
{
lock(locker){ total += local; }
}
);
Output of this is: 21081852648.7199
Now using PLINQ
double tot = ParallelEnumerable.Range(1, 10000000)
.Sum(i => Math.Sqrt(i));
Output of this is: 21081852648.72
Why there is difference between PLINQ output and Parallel.For and Sequential for loop?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我强烈怀疑这是因为双精度数算术并不是真正的关联性。对值求和时可能会丢失信息,并且丢失的具体信息取决于操作的顺序。
下面是一个展示这种效果的例子:
在第一种情况下,我们可以多次添加非常小的数字,直到它们变得足够大,在添加到 1 时仍然相关。
在第二种情况下,将 0.00000000000000001 添加到 1 总是只会得到 1,如下所示双精度中没有足够的信息来表示 1.00000000000000001 - 所以最终结果仍然只是 1。
编辑:我想到了另一个可能令人困惑的方面。对于局部变量,JIT 编译器能够(并且允许)使用 80 位 FP 寄存器,这意味着可以在更少的信息丢失的情况下执行算术。对于必须是 64 位的实例变量来说,情况并非如此。在您的 Parallel.For 示例中,total 变量实际上是生成的类中的实例变量,因为它是由 lambda 表达式捕获的。这可能会改变结果 - 但这很可能取决于计算机体系结构、CLR 版本等。
I strongly suspect it's because arithmetic with doubles isn't truly associative. Information is potentially lost while summing values, and exactly what information is lost will depend on the order of the operations.
Here's an example showing that effect:
In the first case, we can add very small numbers lots of times until they become big enough to still be relevant when added to 1.
In the second case, adding 0.00000000000000001 to 1 always just results in 1 as there isn't enough information in a double to represent 1.00000000000000001 - so the final result is still just 1.
EDIT: I've thought of another aspect which could be confusing things. For local variables, the JIT compiler is able to (and allowed to) use the 80-bit FP registers, which means arithmetic can be performed with less information loss. That's not the case for instance variables which definitely have to be 64-bit. In your Parallel.For case, the
total
variable will actually be an instance variable in a generated class because it's captured by a lambda expression. This could change the results - but it may well depend on computer architecture, CLR version etc.