3

在下面的代码片段中,如果将 Do 替换为 ParallelDo,它将评估 3 倍慢,因为现在循环将仅在两个内核中的一个中中断。

ParallelEvaluate[NN = 10070];
SetSharedVariable[res]
Module[{a, b, c},
  Do[
   c = NN - a - b;  
   If[a a + b b == c c, res = a b c; Break[]]
   , {a, 1, NN}, {b, a + 1, NN}
   ];
  res
  ] // AbsoluteTiming

调用 ParallelAbort 可以解决问题,但这是被禁止的。那里还有什么?

4

1 回答 1

4

您需要为每次迭代提供一种方法来告诉所有其他迭代已找到答案。我用一个“退出”标志对此进行建模,最初设置为 false,当任何迭代决定完成时设置为 true。每次迭代同样必须检查退出条件。

我的 Mathematica 已经生锈了 15 年,而且我之前没有见过 Parallelxxx 形式,但是对于循环应该如何改变的一个很好的猜测是你的代码的以下变化:

ParallelEvaluate[NN = 10070];
SetSharedVariable[res,quit]
Module[{a, b, c},
    quit=false;
   Do[ c = NN - a - b;  
       If[quit, Break[]];
       If[ a a + b b == c c, quit=true; res = a b c; Break[]],
       {a, 1, NN}, {b, a + 1, NN}
     ];
     res
   ] // AbsoluteTiming

额外的 If 在某种程度上减慢了循环,但这就是同步的代价。

我怀疑你在每次迭代中所做的工作量与并行执行每次迭代的成本相比已经非常小了,所以这个循环可能效率低下,你可能无法从 Do Parallel 中获得任何真正的价值。如果你不这样做,那么你可以让每个 Do 迭代对 a 和 b 的多个值进行操作(例如,使用 {a, 1, NN, 10} 和类似地用于 b 每次迭代并将 10 宽的子范围作为内部的子循环处理每次并行迭代)。与每个循环体中完成的工作相比,保持退出测试退出开销较小。重新编码练习留给读者。

您的代码还有另一个问题:设置 res 时存在竞争条件。在某些情况下,两次迭代都可以决定设置 res。如果您不在乎产生了哪个答案,并且 res 的存储是“原子的”,那很好。如果 res 是一个更复杂的数据结构,并且更新它需要多个 Mathematica 语句,那么您肯定会遇到数据竞争,并且您的循环会在很长一段时间内产生不好的结果,并且很难调试。理想情况下,您需要某种原子测试来保护退出条件。我不知道 MMa 中是什么,所以你必须查一下,但我想像一个“原子[...]”形式,它坚持它的主体仅由许多并行线程中的一个执行。(也许 MMa 有一个信号量,你可以用它来实现原子)。如果是这样,

ParallelEvaluate[NN = 10070];
SetSharedVariable[res,quit]
Module[{a, b, c},
    quit=false;
   Do[ c = NN - a - b;  
       If[quit, Break[]];
       If[ a a + b b == c c, 
           atomic[If[not[quit]; quit=true; res = a b c;]; Break[]],
       {a, 1, NN}, {b, a + 1, NN}
     ];
     res
   ] // AbsoluteTiming
于 2009-08-19T07:12:46.323 回答