10

Ho lavorato intensamente a livello di elaborazione in F #. Funzioni come Array.Parallel.map che utilizzano la libreria parallela di attività .Net hanno velocizzato il mio codice in modo esponenziale per uno sforzo davvero minimo.F # PSeq.iter non sembra utilizzare tutti i core

Tuttavia, a causa di problemi di memoria, ho rifatto una sezione del mio codice in modo che possa essere pigramente valutata all'interno di un'espressione di sequenza (questo significa che devo memorizzare e passare meno informazioni). Quando è arrivato il momento di valutare che ho usato:

// 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 

Invece di:

// 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 

Quando si utilizza una delle funzioni Array.Parallel posso vedere chiaramente tutti i core sul mio computer calcio in marcia (~ 100% di utilizzo della CPU). Tuttavia, la memoria extra richiesta significa che il programma non è mai stato completato.

Con la versione PSeq.iter quando eseguo il programma, c'è solo un utilizzo dell'8% della CPU (e un utilizzo minimo della RAM).

Quindi: C'è qualche motivo per cui la versione PSeq viene eseguita molto più lentamente? È a causa della valutazione pigra? C'è qualcosa di magico "essere parallelo" che mi manca?

Grazie,

Altre risorse, implementazioni del codice sorgente di entrambi (sembrano utilizzare diverse librerie parallele in .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

EDIT: Aggiunto di più dettagli per codice esempi e dettagli

Codice:

  • 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 
    
  • Array

    // 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 
    

Dettagli:

  • La memorizzazione del Intermediat la versione dell'array viene eseguita rapidamente (fino a quando si arresta in modo anomalo) in meno di 10 minuti, ma utilizza ~ 70 GB di RAM prima che si blocchi (64 GB fisici, il resto paging)
  • La versione seq impiega oltre 34 minuti e utilizza una frazione della RAM (solo circa 30 GB)
  • C'è un miliardo di valori che sto calcolando. Quindi un miliardo di doppi (a 64 bit ciascuno) = 7.4505806 GB. Ci sono forme di dati più complesse ... e alcune copie non necessarie sto pulendo da qui l'attuale massiccia utilizzo della RAM.
  • Sì l'architettura non è grande, la valutazione pigra è la prima parte di me cercando di ottimizzare il programma e/o lotto il backup dei dati in blocchi più piccoli
  • Con un set di dati più piccolo, entrambi i pezzi di codice di uscita dello stesso risultati.
  • @pad, ho provato quello che mi hai suggerito, il PSeq.iter sembrava funzionare correttamente (tutti i core attivi) quando alimentava il Calculation [], ma c'è ancora il problema della RAM (alla fine si è schiantato)
  • sia la parte di riepilogo del codice che la parte di calcolo hanno un uso intensivo della CPU (principalmente perché di grandi insiemi di dati)
  • Con la versione Seq ho solo lo scopo di parallelizzare una volta
+1

La valutazione lenta non funziona correttamente con l'esecuzione parallela. Per essere onesti, passare lo stesso 'Calcolo []' a 'PSeq.iter' e' Array.Parallel.map'. È impossibile dire la ragione senza avere maggiori dettagli di 'Calculation' e' someFuncToExtractResults'. – pad

+0

Grazie per il suggerimento, ho provato questo e PSeq si comporta bene quando dato l'array invece sul pigro seq ... tuttavia non risolve il problema RAM –

risposta

5

sulla base le informazioni aggiornate, che sto accorciando la mia risposta al solo la parte rilevante. Hai solo bisogno di questo invece di quello che attualmente si dispone:

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

E questo funzionerà lo stesso se si utilizza PSeq.map o Array.Parallel.map.

Tuttavia, il tuo vero problema non sarà risolto. Questo problema può essere indicato come: quando si raggiunge il grado desiderato di lavoro parallelo per ottenere il 100% di utilizzo della CPU, non c'è abbastanza memoria per supportare i processi.

Riesci a vedere come questo non verrà risolto? È possibile elaborare le cose in modo sequenziale (meno efficiente della CPU, ma efficiente in termini di memoria) oppure è possibile elaborare le cose in parallelo (maggiore efficienza della CPU, ma esaurimento della memoria).

Le opzioni sono quindi:

  1. Modifica il grado di parallelismo per essere utilizzati da queste funzioni per qualcosa che non lascerà a bocca memoria:

    let result = data 
          |> PSeq.withDegreeOfParallelism 2 
          |> PSeq.map (calculationFunc >> someFuncToExtractResults) 
    
  2. cambiare la logica di fondo per calculationFunc >> someFuncToExtractResults in modo che sia una singola funzione più efficiente e trasmette i dati ai risultati. Senza conoscere più dettagli, non è semplice vedere come ciò potrebbe essere fatto. Ma internamente, certamente alcuni carichi pigri potrebbero essere possibili.

+0

Entrambi sono intensi, Non sono sicuro di cosa intendi il mio secondo punto, puoi elaborare per favore? –

+0

@AnthonyTruskinger: ho apportato alcuni aggiornamenti significativi in ​​base alle informazioni aggiuntive fornite. Si noti che è necessario scegliere un compromesso da qualche parte se non si desidera modificare l'algoritmo (non si otterrà CPU al 100% e memoria efficiente senza modificare l'algoritmo). Se puoi cambiare l'algoritmo, bene, vedi la mia risposta. – yamen

3

Array.Parallel.map utilizza Parallel.For sotto il cofano mentre PSeq è un sottile involucro intorno PLINQ. Ma la ragione per cui si comportano diversamente qui non è sufficiente per i carichi di lavoro per PSeq.iter quando seq<Calculation> è sequenziale e troppo lento nel produrre nuovi risultati.

Non mi viene in mente l'utilizzo di seq o array intermedi. Supponiamo data essere l'array di input, spostando tutti i calcoli in un luogo è la strada da percorrere:

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

e

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

È evitare di consumare troppa memoria e avere come computazione in un luogo che conduce per migliorare l'efficienza nell'esecuzione parallela.

Problemi correlati