我一直在F#做一些计算密集型的工作。像Array.Parallel.map
这样使用.Net任务并行库的函数以指数方式加速了我的代码,但实际上只需要很少的工作量。
然而,由于内存问题,我重新编写了我的代码的一部分,以便可以在序列表达式中进行延迟评估(这意味着我必须存储并传递较少的信息)。当评估我使用的时候:
// processor and memory intensive task, results are not stored
let calculations : seq<Calculation> = seq { ...yield one thing at a time... }
// extract results from calculations for summary data
PSeq.iter someFuncToExtractResults results
而不是:
// processor and memory intensive task, storing these results is an unnecessary task
let calculations : Calculation[] = ...do all the things...
// extract results from calculations for summary data
Array.Parallel.map someFuncToExtractResults calculations
当使用任何Array.Parallel函数时,我可以清楚地看到计算机上的所有核心都开始运转(CPU使用率约为100%)。但是,所需的额外内存意味着程序永远不会完成。
运行程序时使用PSeq.iter版本,CPU使用率仅为8%(并且RAM使用率最低)。
所以:PSeq版本运行得如此之慢有什么原因吗?是因为懒惰的评价?我缺少一些神奇的“平行”的东西吗?
谢谢,
其他资源,两者的源代码实现(它们似乎在.NET中使用不同的并行库):
https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/array.fs
https://github.com/fsharp/powerpack/blob/master/src/FSharp.PowerPack.Parallel.Seq/pseq.fs
编辑:为代码示例和详细信息添加了更多详细信息
代码:
SEQ
// processor and memory intensive task, results are not stored
let calculations : seq<Calculation> =
seq {
for index in 0..data.length-1 do
yield calculationFunc data.[index]
}
// extract results from calculations for summary data (different module)
PSeq.iter someFuncToExtractResults results
阵列
// processor and memory intensive task, storing these results is an unnecessary task
let calculations : Calculation[] =
Array.Parallel.map calculationFunc data
// extract results from calculations for summary data (different module)
Array.Parallel.map someFuncToExtractResults calculations
详细说明:
答案 0 :(得分:6)
根据您的最新信息,我缩短了对相关部分的回答。你只需要这个而不是现在的东西:
let result = data |> PSeq.map (calculationFunc >> someFuncToExtractResults)
无论您使用PSeq.map
还是Array.Parallel.map
,这都是一样的。
但是,你真正的问题不会得到解决。这个问题可以表述为:当达到所需的并行工作程度以达到100%的CPU使用率时,没有足够的内存来支持这些过程。
你能看到这个怎么解决不了吗?您可以按顺序处理事务(CPU效率更低,但内存效率更高),也可以并行处理(更高CPU效率,但内存不足)。
然后选项是:
将这些函数使用的并行度更改为不会破坏内存的内容:
let result = data
|> PSeq.withDegreeOfParallelism 2
|> PSeq.map (calculationFunc >> someFuncToExtractResults)
更改calculationFunc >> someFuncToExtractResults
的基础逻辑,使其成为更高效的单个函数,并将数据流式传输到结果中。在不了解更多细节的情况下,看到如何做到这一点并不容易。但在内部,肯定会有一些延迟加载。
答案 1 :(得分:4)
Array.Parallel.map
使用Parallel.For
,PSeq
是PLINQ
周围的薄包装。但是他们在这里表现不同的原因是PSeq.iter
在seq<Calculation>
是顺序的时候没有足够的工作量,而在产生新结果时太慢。
我不知道使用中间seq或数组。假设data
是输入数组,在一个地方移动所有计算是可行的方法:
// Should use PSeq.map to match with Array.Parallel.map
PSeq.map (calculationFunc >> someFuncToExtractResults) data
和
Array.Parallel.map (calculationFunc >> someFuncToExtractResults) data
您可以避免消耗太多内存并在一个地方进行密集计算,从而提高并行执行的效率。
答案 2 :(得分:1)
我遇到了与您类似的问题,并通过将以下内容添加到解决方案的App.config文件中来解决了该问题:
<runtime>
<gcServer enabled="true" />
<gcConcurrent enabled="true"/>
</runtime>
计算占用5'49''并显示Process Lasso上大约22%的CPU使用率,而花费1'36''显示大约80%的CPU使用率。
另一个可能影响并行代码速度的因素是在BIOS中是否启用了超线程(Intel)或SMT(AMD)。我看到了禁用导致更快执行的情况。