具有Interlocked.CompareExchange()的Parallel.For():性能较差,结果与串行版本略有不同 [英] Parallel.For() with Interlocked.CompareExchange(): poorer performance and slightly different results to serial version

查看:108
本文介绍了具有Interlocked.CompareExchange()的Parallel.For():性能较差,结果与串行版本略有不同的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试使用Parallel.For()计算列表的平均值.我决定反对它,因为它比简单的串行版本慢大约四倍.但是,我对它产生的结果与序列结果不完全相同感到好奇,我认为了解其原因将是有启发性的.

我的代码是:

public static double Mean(this IList<double> list)
{
        double sum = 0.0;


        Parallel.For(0, list.Count, i => {
                        double initialSum;
                        double incrementedSum;
                        SpinWait spinWait = new SpinWait();

                        // Try incrementing the sum until the loop finds the initial sum unchanged so that it can safely replace it with the incremented one.
                        while (true) {
                            initialSum = sum;
                            incrementedSum = initialSum + list[i];
                            if (initialSum == Interlocked.CompareExchange(ref sum, incrementedSum, initialSum)) break;
                            spinWait.SpinOnce();
                        }
                     });

        return sum / list.Count;
    }

当我在2000000点的随机序列上运行代码时,我得到的结果与序列均值的后两位数字不同.

我搜索了stackoverflow,并发现了以下内容:解决方案

使用 double 是潜在的问题,通过使用 long ,您可以更好地了解同步不是原因em>代替.您得到的结果实际上是正确的,但是永远不会令程序员满意.

您发现浮点数学是可以交流的,但是不具有关联性.换句话说,就是a + b == b + a而不是a + b + c != a + c + b.在您的代码中隐含了数字的添加顺序是非常随机的.

这个C ++问题也对此进行了讨论.

I experimented with calculating the mean of a list using Parallel.For(). I decided against it as it is about four times slower than a simple serial version. Yet I am intrigued by the fact that it does not yield exactly the same result as the serial one and I thought it would be instructive to learn why.

My code is:

public static double Mean(this IList<double> list)
{
        double sum = 0.0;


        Parallel.For(0, list.Count, i => {
                        double initialSum;
                        double incrementedSum;
                        SpinWait spinWait = new SpinWait();

                        // Try incrementing the sum until the loop finds the initial sum unchanged so that it can safely replace it with the incremented one.
                        while (true) {
                            initialSum = sum;
                            incrementedSum = initialSum + list[i];
                            if (initialSum == Interlocked.CompareExchange(ref sum, incrementedSum, initialSum)) break;
                            spinWait.SpinOnce();
                        }
                     });

        return sum / list.Count;
    }

When I run the code on a random sequence of 2000000 points, I get results that are different in the last 2 digits to the serial mean.

I searched stackoverflow and found this: VB.NET running sum in nested loop inside Parallel.for Synclock loses information. My case, however, is different to the one described there. There a thread-local variable temp is the cause of inaccuracy, but I use a single sum that is updated (I hope) according to the textbook Interlocked.CompareExchange() pattern. The question is of course moot because of the poor performance (which surprises me, but I am aware of the overhead), yet I am curious whether there is something to be learnt from this case.

Your thoughts are appreciated.

解决方案

Using double is the underlying problem, you can feel better about the synchronization not being the cause by using long instead. The results you got are in fact correct but that never makes a programmer happy.

You discovered that floating point math is communicative but not associative. Or in other words, a + b == b + a but a + b + c != a + c + b. Implicit in your code that the order in which the numbers are added is quite random.

This C++ question talks about it as well.

这篇关于具有Interlocked.CompareExchange()的Parallel.For():性能较差,结果与串行版本略有不同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆