我遇到以下代码的问题。代码没有错误,但是当使用并行for循环和常规for循环时,我会收到不同的输出值。我需要使并行for循环正常工作,因为我运行此代码数千次。有谁知道为什么我的并行for循环返回不同的输出?
test1 = 137431.12889999992 (parallel for loop)
test2 = 7.3770258447689254E- (regular for loop)
DataSET
这可能是一个公平的方法来准备一个确实完全可重现的设置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请求答复或帮助的其他社区成员。
我的新版代码:
{{1}}
答案 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();
}
就是你现在循环内部的东西。它必须包含DoIt
和i
以及您需要传递给它的其他任何向量。它必须仅计算一个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
循环的并行度。甚至没有保证它将并行运行。
press1
和press2
不是本地变量,因此做需要以某种方式同步这些变量。 (如果你找到一些方法来避免每次锁定会更好,因为这至少会部分地扼杀多线程的点。)
也许最关键的是,ConcurrentBag
是一个无序的集合。您没有显示您在矩阵上执行的所有操作,但如果您在任何地方进行矩阵乘法,则很容易导致错误的结果。 无法保证矩阵乘法会通勤。虽然A * B = B * A
表示整数,但对于矩阵,这通常 为真。很可能你的逻辑巧妙地依赖于以特定顺序发生的操作(并且它们不会因为ConcurrentBag
是无序的)。