10

私は F# で計算集約的な作業を行ってきました。.Net Task Parallel Library を使用するようなArray.Parallel.map関数は、非常に最小限の労力で私のコードを指数関数的に高速化しました。

ただし、メモリの問題により、コードのセクションを作り直して、シーケンス式内で遅延評価できるようにしました (これは、格納して渡す情報が少なくなることを意味します)。評価する時が来たとき、私は以下を使用しました:

// 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 関数のいずれかを使用すると、コンピューターのすべてのコアがギアを上げていることがはっきりとわかります (~100% の CPU 使用率)。ただし、追加のメモリが必要になるということは、プログラムが終了しないことを意味します。

プログラムを実行したときの PSeq.iter バージョンでは、約 8% の CPU 使用率 (および最小限の RAM 使用率) しかありません。

では、PSeq バージョンの実行速度がこれほど遅いのには何か理由があるのでしょうか? 遅延評価のせいですか?私が見逃している魔法の「平行になる」ものはありますか?

ありがとう、

その他のリソース、両方のソース コード実装 (.NET では異なる Parallel ライブラリを使用しているようです):

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

編集:コード例と詳細に詳細を追加

コード:

  • シーケンス

    // 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 のみ) を使用します。
  • 私が計算している〜十億の値があります。したがって、10 億倍 (それぞれ 64 ビット) = 7.4505806GB です。より複雑な形式のデータがあります...そしていくつかの不要なコピーをクリーンアップしているため、現在大量の RAM が使用されています。
  • はい、アーキテクチャは良くありません。遅延評価は、プログラムを最適化したり、データを小さなチャンクにまとめたりする最初の部分です。
  • より小さいデータセットでは、コードの両方のチャンクが同じ結果を出力します。
  • @pad、私はあなたが提案したことを試しました.PSeq.iterはCalculation []を供給されたときに適切に動作するように見えました(すべてのコアがアクティブです)が、まだRAMの問題があります(最終的にクラッシュしました)
  • コードの要約部分と計算部分の両方が CPU を集中的に使用します (主にデータ セットが大きいため)。
  • Seq バージョンでは、一度だけ並列化することを目指しています
4

3 に答える 3

5

更新された情報に基づいて、回答を関連部分だけに短縮しています。現在持っているものの代わりにこれが必要です:

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より効率的でデータを結果にストリーミングする単一の関数になるようにします。詳細を知らなければ、これがどのように行われるかを理解するのは簡単ではありません。しかし、内部的には、確かに遅延読み込みが可能な場合があります。

于 2012-04-17T05:11:37.313 に答える
3

Array.Parallel.mapParallel.Forボンネットの下で使用しますPSeqが、薄いラッパーPLINQです。PSeq.iterしかし、ここで動作が異なる理由は、シーケンシャルな場合に十分なワークロードがなくseq<Calculation>、新しい結果を生成するのが遅すぎるためです。

中間の seq または配列を使用するという考えがわかりません。入力配列であると仮定dataすると、すべての計算を 1 か所に移動することができます。

// Should use PSeq.map to match with Array.Parallel.map
PSeq.map (calculationFunc >> someFuncToExtractResults) data

Array.Parallel.map (calculationFunc >> someFuncToExtractResults) data

大量のメモリを消費することを避け、集中的な計算を 1 か所で行うことで、並列実行の効率が向上します。

于 2012-04-17T06:57:11.163 に答える