10

Wykonałem jakąś intensywną obliczeniowo pracę w F #. Funkcje takie jak Array.Parallel.map, które korzystają z biblioteki zadań interfejsu .Net, przyspieszyły mój kod wykładniczo dla naprawdę minimalnego wysiłku.F # PSeq.iter wydaje się nie używać wszystkich rdzeni

Jednak ze względu na problemy z pamięcią, przerobiłem część mojego kodu tak, aby można ją było leniwie ocenić w wyrażeniu sekwencji (to znaczy, że muszę przechowywać i przekazywać mniej informacji). Kiedy nadszedł czas, aby ocenić użyłem:

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

Zamiast:

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

Przy zastosowaniu dowolnej funkcji Array.Parallel mogę wyraźnie zobaczyć wszystkie rdzenie na moim opuszcza komputer w biegu (~ 100% użycie procesora). Jednak wymagana dodatkowa pamięć oznacza, że ​​program nigdy się nie zakończył.

W wersji PSeq.iter po uruchomieniu programu zużywa się tylko około 8% (i minimalne użycie pamięci RAM).

A więc: Czy jest jakiś powód, dla którego wersja PSeq działa znacznie wolniej? Czy to z powodu leniwej oceny? Czy jest jakiś magiczny "be parallel", którego mi brakuje?

Dzięki,

innych zasobów implementacje kodu źródłowego zarówno (wydają się korzystać z różnych bibliotek równolegle 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: Dodano więcej szczegóły dotyczące przykładów kodu i szczegółów

Kod:

  • 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 
    

Szczegóły:

  • przechowywania na częstotliwo Wersja e-array działa szybko (aż do momentu awarii) w czasie poniżej 10 minut, ale wymaga ~ 70 GB pamięci RAM, zanim się zawiesi (64 GB fizycznej, reszta stronicowana).
  • Wersja seq zajmuje ponad 34 minuty i wykorzystuje ułamek pamięci RAM (tylko około 30 GB)
  • Obliczam ~ miliardów wartości. Stąd miliard podwójnych (po 64 bity) = 7.4505806GB. Są bardziej złożone formy danych ... i kilka niepotrzebnych kopii, które czyszczę, a więc obecne ogromne wykorzystanie pamięci RAM.
  • Tak, architektura nie jest wspaniała, leniwy wynik jest pierwszą częścią mnie, która próbuje zoptymalizować program i/lub wsadowe dane na mniejsze porcje.
  • W przypadku mniejszego zestawu danych, oba fragmenty kodu wyprowadzają te same wartości. wyniki.
  • @pad, próbowałem tego, co sugerujesz, PSeq.iter wydawał się działać poprawnie (wszystkie rdzenie aktywne) po zasileniu Kalkulacją [], ale nadal pozostaje kwestia pamięci RAM (ostatecznie uległa awarii)
  • zarówno część podsumowująca kodu, jak i część obliczeniowa są obciążone przez procesor (głównie dlatego, że dużych zbiorów danych)
  • z wersją Sekw po prostu dążyć do parallelize raz
+1

Ocena leniwy nie jest przyjemna przy wykonywaniu równoległym. Aby być sprawiedliwym, przekaż te same 'Obliczenia []' do 'PSeq.iter' i' Array.Parallel.map'. Nie można podać przyczyny bez posiadania więcej szczegółów na temat 'Obliczenia' i' niektóreFuncToExtractResults'. – pad

+0

Dziękuję za sugestię, próbowałem tego i PSeq zachowuje się dobrze, gdy otrzymam tablicę zamiast leniwego seq ... jednak nie rozwiązuje problemu z pamięcią –

Odpowiedz

5

na podstawie aktualnych informacji, jestem skrócenie moją odpowiedź na tylko odpowiedniej części. Po prostu trzeba to zamiast tego, co masz aktualnie:

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

A to będzie działać tak samo, czy używasz PSeq.map lub Array.Parallel.map.

Jednak twój prawdziwy problem nie zostanie rozwiązany. Ten problem można określić jako: gdy osiągnięty zostanie pożądany stopień pracy równoległej w celu uzyskania 100% użycia procesora, brakuje pamięci do obsługi procesów.

Czy widzisz, jak to nie zostanie rozwiązane? Możesz albo przetwarzać rzeczy sekwencyjnie (mniej wydajnie CPU, ale wydajnie pod względem pamięci) lub możesz przetwarzać dane równolegle (więcej wydajnego procesora, ale zabraknie mu pamięci).

Opcje następnie są:

  1. Zmień stopień równoległości być wykorzystywane przez tych funkcji do czegoś, co nie będzie cios Pamięć:

    let result = data 
          |> PSeq.withDegreeOfParallelism 2 
          |> PSeq.map (calculationFunc >> someFuncToExtractResults) 
    
  2. zmieniają podstawowej logiki calculationFunc >> someFuncToExtractResults dzięki czemu jest to jedna funkcja, która jest bardziej wydajna i przekazuje dane do wyników. Nie wiedząc więcej szczegółów, nie jest łatwo zobaczyć, jak można to zrobić. Ale wewnętrznie, z pewnością pewne leniwy ładunek może być możliwy.

+0

Oba są intensywne, Nie jestem pewien, co masz na myśli mój drugi punkt, możesz rozwinąć proszę? –

+0

@AnthonyTruskinger: Wprowadziłem kilka istotnych aktualizacji na podstawie dodatkowych informacji, które podałeś. Zauważ, że musisz wykupić gdzieś, jeśli nie chcesz, aby algorytm się zmienił (nie dostaniesz 100% CPU i wydajnej pamięci bez zmiany algorytmu). Jeśli możesz zmienić algorytm, dobrze, zobacz moją odpowiedź. – yamen

3

Array.Parallel.map wykorzystuje Parallel.For pod wyciągiem podczas PSeq jest cienką owinięcie wokół PLINQ. Ale powodem, dla którego zachowują się inaczej, jest brak wystarczających obciążeń dla PSeq.iter, gdy seq<Calculation> jest sekwencyjny i zbyt wolny w uzyskiwaniu nowych wyników.

Nie mam pojęcia o użyciu pośredniego seq lub tablicy. Załóżmy data być tablica wejściowa, przenosząc wszystkie obliczenia w jednym miejscu jest droga:

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

i

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

unikać spożywania zbyt dużej ilości pamięci i mają intensywne obliczenia w jednym miejscu, co prowadzi dla lepszej wydajności w równoległym wykonaniu.

Powiązane problemy