Wolfram mathematica Mathematica中并行计算的进度监控

Wolfram mathematica Mathematica中并行计算的进度监控,wolfram-mathematica,progress-bar,parallel-processing,Wolfram Mathematica,Progress Bar,Parallel Processing,我正在构建一个大型的并行表,并希望对计算过程保持一定的了解。对于非并行表,以下代码非常有用: counter = 1; Timing[ Monitor[ Table[ counter++ , {n, 10^6}]; , ProgressIndicator[counter, {0, 10^6}] ] ] 结果是{0.943512,Null}。然而,对于并行情况,有必要在内核之间共享计数器: counter = 1; SetSharedVariable[counter]; Ti

我正在构建一个大型的
并行表
,并希望对计算过程保持一定的了解。对于非并行表,以下代码非常有用:

counter = 1;
Timing[
 Monitor[
  Table[
   counter++
  , {n, 10^6}];
 , ProgressIndicator[counter, {0, 10^6}]
 ]
]
结果是
{0.943512,Null}
。然而,对于并行情况,有必要在内核之间共享
计数器

counter = 1;
SetSharedVariable[counter];
Timing[
 Monitor[
  ParallelTable[
   counter++
  , {n, 10^4}];
 , ProgressIndicator[counter, {0, 10^4}]
 ]
]

结果是
{6.33388,Null}
。由于每次更新时都需要在内核之间来回传递
计数器
的值,因此性能受到的影响非常严重。关于如何理解计算过程有什么想法吗?也许让每个内核都有自己的
计数器值
,并每隔一段时间对它们求和?也许可以用某种方法来确定表中哪些元素已经分配给了内核?

这似乎很难解决。从:

除非使用共享变量,否则将执行并行计算 完全独立,不能相互影响。 此外,任何副作用,如变量赋值 作为评估的一部分发生将丢失。唯一的效果是 并行计算是在最后返回结果

但是,仍然可以使用旧的
Print
语句获得大致的进度指标:


您可以从Yuri Kandrashkin开发的软件包
Spin`System`LoopControl`
中获得一些想法:


当您说“也许让每个内核都有自己的计数器值并每隔一段时间求和”时,您几乎自己给出了答案

试着这样做:

counter = 1;
SetSharedVariable[counter];
ParallelEvaluate[last = AbsoluteTime[]; localcounter = 1;]
Timing[Monitor[
  ParallelTable[localcounter++; 
    If[AbsoluteTime[] - last > 1, last = AbsoluteTime[]; 
     counter += localcounter; localcounter = 0;], {n, 10^6}];, 
  ProgressIndicator[counter, {0, 10^6}]]]
请注意,它比第一个单CPU情况需要更长的时间,因为它实际上在循环中做了一些事情


您可以将测试AbsoluteTime[]-last>1更改为更频繁的测试,如AbsoluteTime[]-last>0.1。

另一种方法是对LinkWrite和LinkRead进行跟踪,并修改其跟踪消息以进行一些有用的记帐

首先,启动一些并行内核:

LaunchKernels[]
这将为并行内核设置链接对象

然后为链接读写计数器定义一个init函数:

init[] := Map[(LinkWriteCounter[#] = 0; LinkReadCounter[#] = 0) &, Links[]]
接下来,您要在读取或写入这些计数器的链接时增加这些计数器:

Unprotect[Message];
Message[LinkWrite::trace, x_, y_] := LinkWriteCounter[x[[1, 1]]] += 1;
Message[LinkRead::trace, x_, y_] := LinkReadCounter[x[[1, 1]]] += 1;
Protect[Message];
这里,
x[[1,1]]
是所讨论的链接对象

现在,在LinkWrite和LinkRead上启用跟踪:

On[LinkWrite];
On[LinkRead];
要设置进度显示的格式,请先将LinkObject显示缩短一点,因为它们相当冗长:

Format[LinkObject[k_, a_, b_]] := Kernel[a, b]
这是一种动态显示子内核链接读写的方法:

init[];
Dynamic[Grid[Join[
  {{"Kernel", "Writes", "Reads"}}, 
  Map[{#, LinkWriteCounter[#]/2, LinkReadCounter[#]/2} &, 
  Select[Links[], StringMatchQ[First[#], "*subkernel*"] &
]]], Frame -> All]]
(我将计数除以2,因为每个读写链接都被跟踪两次)

最后用一个10000元素表进行测试:

init[];
ParallelTable[i, {i, 10^4}, Method -> "FinestGrained"];
如果一切正常,您应该会看到最终的进度显示,每个内核大约有5000次读写操作:


这会造成中等性能损失:不使用显示器时为10.73秒,使用显示器时为13.69秒。当然,对于这种特殊的并行计算,使用“细粒度”选项并不是最理想的方法。

也许应该强调的是,第二个循环的迭代次数比第一个循环少100次,因此,性能受到的影响比乍一看更严重。@Sjoerd我没有测试过,但可能没有。我将此作为一个类似功能的示例发布,如果有人希望实现它,它可能会很有用。@Sjoerd为了并行工作而扩展此方法可能并不太困难。我们唯一需要了解的是如何在并行子内核中更改
动态
变量的值。另外:可以使用
PrintTemporary
而不是
Print
。我认为在这种情况下,这个函数非常有用。我已经试过了。它在并行情况下似乎不起作用。@Sjoerd C.de Vries:您可以使用共享函数来确保PrintTemporary始终在主内核上运行:SetSharedFunction[ParallelPrintTemporary];ParallelPrintTemporal[e_3;]:=PrintTemporal[e];计时[ParallelTable[If[Mod[n,100000]==0,ParallelPrintTemporary[n]];,{n,10^6}];]好主意!然而,所有那些对
AbsoluteTime[]
的调用似乎也会导致相当严重的打击。我的解决方案是简单地允许
localcounter
运行到10000,然后将其转储到全局
计数器中。谢谢公平地说,这取决于你在循环中做了什么。我发现AbsoluteTime[]大约需要7个增量(++)操作,这并不太糟糕:比较k=0;计时[表[k++;k++;k++;k++;k++;k++;k++;{10^6}];]与计时[表[AbsoluteTime[];,{10^6}];]一起使用
AbsoluteTiming
代替您答案中代码中的计时,我得到18秒,而使用转储每10000次技术,我得到2.5秒。由于内核之间的通信在后一种情况下要频繁得多,我只能假设是
AbsoluteTime[]
增加了这么多的时间。实际上,在测试您的注释中的代码片段时,我发现第一个用了5秒,第二个用了28秒。想知道我们的系统之间有什么不同,使得
AbsoluteTime[]
对我来说花费的时间更长。
init[];
ParallelTable[i, {i, 10^4}, Method -> "FinestGrained"];