问题 F#PSeq.iter似乎没有使用所有核心


我一直在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 
    

细节:

  • 存储中间阵列版本在10分钟内快速运行(在崩溃之前),但在崩溃之前使用~70GB RAM(64GB物理,其余分页)
  • seq版本需要34分钟并使用一小部分RAM(仅约30GB)
  • 我正在计算出数十亿的价值。因此,十亿双打(每个64位)= 7.4505806GB。还有更复杂的数据形式......还有一些不必要的副本,我正在清理当前大量的RAM使用情况。
  • 是的,架构不是很好,懒惰的评估是我尝试优化程序和/或将数据批量分成较小块的第一部分
  • 使用较小的数据集,两个代码块都会输出相同的结果。
  • @pad,我尝试了你的建议,PSeq.iter似乎正常工作(所有核心都处于活动状态),当计算[]时,但仍然存在RAM问题(它最终崩溃)
  • 代码的汇总部分和计算部分都是CPU密集型的(主要是因为大型数据集)
  • 使用Seq版本我只想实现一次并行化

9284
2018-04-17 04:22


起源

懒惰评估对并行执行不起作用。公平地说,通过相同的 Calculation[] 至 PSeq.iter 和 Array.Parallel.map。如果没有更详细的信息,就无法说出原因 Calculation 和 someFuncToExtractResults。 - pad
感谢您的建议,我尝试了这个,并且PSeq在给定数组而不是懒惰的seq时表现良好......但是它没有解决RAM问题 - Anthony Truskinger


答案:


根据您的最新信息,我缩短了我对相关部分的回答。你只需要这个,而不是你现在拥有的:

let result = data |> PSeq.map (calculationFunc >> someFuncToExtractResults)

无论你使用什么,这都会起作用 PSeq.map 要么 Array.Parallel.map

但是,你真正的问题不会得到解决。这个问题可以表述为:当达到所需的并行工作程度以达到100%的CPU使用率时,没有足够的内存来支持这些进程。

你能看到这个怎么解决不了吗?您可以按顺序处理事务(CPU效率较低,但内存效率较低),也可以并行处理事务(CPU效率更高,但内存不足)。

那么选项是:

  1. 将这些函数使用的并行度更改为不会破坏内存的内容:

    let result = data 
                 |> PSeq.withDegreeOfParallelism 2 
                 |> PSeq.map (calculationFunc >> someFuncToExtractResults)
    
  2. 更改基础逻辑 calculationFunc >> someFuncToExtractResults 这样它就是一个更有效的单一功能,并将数据流式传输到结果中。在不了解更多细节的情况下,看到如何做到这一点并不容易。但在内部,肯定会有一些延迟加载。


6
2018-04-17 05:11



两者都很密集,我不确定你的意思我的第二点,你能详细说明吗? - Anthony Truskinger
@AnthonyTruskinger:我根据您提供的额外信息进行了一些重要更新。请注意,如果您不希望更改算法,则必须在某处选择权衡(在不更改算法的情况下,您将无法获得100%的CPU和高效内存)。如果您可以更改算法,那么请参阅我的答案。 - yamen


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

您可以避免消耗太多内存并在一个地方进行密集计算,从而提高并行执行的效率。


4
2018-04-17 06:57





我有一个类似于你的问题,并通过在解决方案的App.config文件中添加以下内容来解决它:

<runtime> 
    <gcServer enabled="true" />
    <gcConcurrent enabled="true"/>
</runtime>

计算采用5'49'并显示Process Lasso大约22%的CPU利用率需要1'36'',显示大约80%的CPU利用率。

另一个可能影响并行化代码速度的因素是BIOS中是否启用了超线程(Intel)或SMT(AMD)。我见过禁用导致执行速度加快的情况。


1
2018-06-24 01:07