如何修复Parallel for() - 循环不返回与()循环的串行不同的值?

时间:2017-11-12 20:33:11

标签: c# multithreading parallel-processing

我遇到以下代码的问题。代码没有错误,但是当使用并行for循环和常规for循环时,我会收到不同的输出值。我需要使并行for循环正常工作,因为我运行此代码数千次。有谁知道为什么我的并行for循环返回不同的输出?

test1 = 137431.12889999992 (parallel for loop)

test2 = 7.3770258447689254E- (regular for loop)

实际输出(更新结果):

DataSET

结语:如何设置符合MCVE的测试

这可能是一个公平的方法来准备一个确实完全可重现的设置MCVE代码+ A / B / C / ... public double CalculatePredictedRSquared() { Vector<double> output = CreateVector.Dense(Enumerable.Range(0, 400).Select(i => Convert.ToDouble(i)).ToArray()); List<double> input1 = new List<double>(Enumerable.Range(0, 400).Select(i => Convert.ToDouble(i))); List<double> input2 = new List<double>(Enumerable.Range(200, 400).Select(i => Convert.ToDouble(i))); double tss = CalculateTotalSumOfSquares(output.ToList()); IEnumerable<int> range = Enumerable.Range(0, output.Count); var query = range.Select(i => DoIt(i, output, input1, input2)); var result = 1 - (query.Sum() / tss); return result; } public double DoIt(int i, Vector<double> output, List<double> input1, List<double> input2) { List<double[]> matrixList = new List<double[]> { input1.Where((v, k) => k != i).ToArray(), input2.Where((v, k) => k != i).ToArray() }; var matrixArray = CreateMatrix.DenseOfColumnArrays(matrixList); var actualResult = output.ElementAt(i); var newVectorArray = CreateVector.Dense(output.Where((v, j) => j != i).ToArray()); var items = FindBestMRSolution(matrixArray, newVectorArray); double estimate = 0; if (items != null) { var y = CalculateYIntercept(matrixArray, newVectorArray, items); for (int m = 0; m < 2; m++) { var coefficient = items[m]; if (m == 0) { estimate += input1.ElementAt(i) * coefficient; } else { estimate += input2.ElementAt(i) * coefficient; } } } else { estimate = 0; } return Math.Pow(actualResult - estimate, 2); } -s,放入一个可立即运行的[ IDE&amp;测试Sandbox,在此处进行超链接] [1],以便社区成员可以单击重新运行按钮并专注于根本原因分析,而不是解码和重新设计不完整SLOC的堆。

如果这是针对O / P运行的,它将运行给O / P请求答复或帮助的其他社区成员。

Try it online!

我的新版代码:

{{1}}

2 个答案:

答案 0 :(得分:12)

这整件事都是狗的早餐;你应该完全放弃这种并行性的尝试。

重新开始。这就是我想要你做的。我希望您编写一个返回if (instructions.Count == 3 && instructions[0].OpCode == OpCodes.Ldarg_0 && instructions[1].OpCode == OpCodes.Ldfld && instructions[2].OpCode == OpCodes.Ret) { FieldInfo backingField = (FieldInfo)instructions[1].Operand; } 的方法DoIt,并使用int double进行循环的单次迭代所需的其他状态

然后,您将按如下方式重写您的方法:

i

知道了吗? public double CalculatePredictedRSquared() { Vector<double> output = whatever; // Whatever other state you need here IEnumerable<int> range = Enumerable.Range(0, output.Count); var query = range.Select(i => DoIt(i, whatever_other_state)); return query.Sum(); } 就是你现在循环内部的东西。它必须包含DoIti以及您需要传递给它的其他任何向量。它必须计算一个double - 在这种情况下,估计误差的平方 - 并返回该double。

它必须是:它不能读取或写入任何非局部变量,它不能调用任何非纯方法,并且在给定相同输入时它必须给出完全相同的结果, 每次。纯方法是编写,读取,理解,测试和并行化的最简单方法; 在进行数学计算时总是尝试编写纯方法

编写output的测试用例,并测试其中的。这是一种纯粹的方法;你应该能够编写大量的测试用例。同样地测试由DoIt调用的任何纯方法。

一旦你对DoIt既正确又纯洁感到满意,那么神奇就会发生。只需将其更改为:

DoIt

然后比较并行和非并行版本。它们应该产生相同的结果;如果没有,那么某些东西是不纯的。弄清楚它是什么。

然后,验证并行版本更快。如果没有,那么你未能在range.AsParallel().Select... 中做足够的工作来证明并行性;有关详细信息,请参阅https://en.wikipedia.org/wiki/Amdahl%27s_law

答案 1 :(得分:0)

一些事情:

lock (_lock)
{
   matrixList.Add(input1.Where((v, k) => k != i).ToArray());
   matrixList.Add(input2.Where((v, k) => k != i).ToArray());
}

您正在将项目添加到设计已经是线程安全的集合中,因此无需锁定。虽然List 是线程安全的,但同时从中读取它应该没问题。来自documentation

  

在List上执行多个读取操作是安全的,但如果在读取集合时修改了集合,则可能会出现问题。要确保线程安全,请在读取或写入操作期间锁定集合。要使多个线程能够访问集合以进行读写,您必须实现自己的同步。

另请注意,matrixList存储在局部变量中;在这种情况下,集合不能从多个线程调用,因为委托的整个主体保证在同一个线程上运行 - 它不会是半身的情况Parallel.For循环将在线程A上运行,而另一半将在线程B上运行,例如。

同样,在对estimate1进行更改时没有理由锁定,因为它无法从其他线程进行修改。

免责声明:无法保证整个Parallel.For循环的并行度。甚至没有保证它并行运行。

然而,

press1press2 不是本地变量,因此需要以某种方式同步这些变量。 (如果你找到一些方法来避免每次锁定会更好,因为这至少会部分地扼杀多线程的点。)

也许最关键的是,ConcurrentBag是一个无序的集合。您没有显示您在矩阵上执行的所有操作,但如果您在任何地方进行矩阵乘法,则很容易导致错误的结果。 无法保证矩阵乘法会通勤。虽然A * B = B * A表示整数,但对于矩阵,这通常 为真。很可能你的逻辑巧妙地依赖于以特定顺序发生的操作(并且它们不会因为ConcurrentBag是无序的)。