10

Я занимаюсь некоторой вычислительной интенсивностью в F #. Такие функции, как Array.Parallel.map, которые используют параллельную библиотеку .Net Task, ускорили мой код экспоненциально для действительно весьма минимальных усилий.F # PSeq.iter, похоже, не использует все ядра

Однако из-за проблем с памятью я переделываю раздел своего кода, чтобы его можно было лениво оценить внутри выражения последовательности (это означает, что я должен хранить и передавать меньше информации). Когда пришло время, чтобы оценить я использовал:

// 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% использования ЦП). Однако требуемая дополнительная память означает, что программа никогда не заканчивается.

С версией PSeq.iter при запуске программы используется только около 8% использования ЦП (и минимальное использование ОЗУ).

Итак: Есть ли причина, по которой версия 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

EDIT: Добавлено больше подробная информация о примерах и деталях

Код:

  • 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 
    

Детали:

  • накапливания в intermediat Версия массива e быстро (насколько это возможно до краха) в течение 10 минут, но использует ~ 70 ГБ оперативной памяти до того, как она сработает (64-гигабайтный физический, остальные постранированы)
  • SEQ-версия занимает более 34 минут и использует часть ОЗУ (только около 30 ГБ)
  • Существует миллиард значений, которые я рассчитываю. Следовательно, миллиард удваивается (по 64 бита каждый) = 7,4505806 ГБ. Есть более сложные формы данных ... и несколько ненужных копий, которые я очищаю, поэтому текущее массовое использование ОЗУ.
  • Да, архитектура невелика, ленивая оценка - это первая часть меня, пытающаяся оптимизировать программу и/или доставлять данные на более мелкие куски.
  • С меньшим набором данных обе части кода выводятся одинаково Результаты.
  • @pad, я попробовал то, что вы предложили, PSeq.Кажется, что он работал нормально (все активные ядра) при подаче Calculation [], но все еще остается вопрос о RAM (он в конечном итоге разбился)
  • как сводная часть кода, так и часть вычисления являются интенсивными CPU (в основном потому, что больших массивов данных)
  • с версии Seq я просто прицелиться распараллелить раз
+1

Ленивая оценка не играет хорошо с параллельным исполнением. Чтобы быть справедливым, передайте те же 'Расчет []' на 'PSeq.iter' и' Array.Parallel.map'. Невозможно рассказать причину, не имея более подробной информации о 'Расчет'' и' someFuncToExtractResults'. – pad

+0

Спасибо за предложение, я попробовал это, и PSeq хорошо себя ведет, когда задан массив вместо ленивого seq ... однако он не решает проблему с RAM –

ответ

5

на основе обновленной информации, я укорачивая мой ответ только соответствующей части. Вам просто нужно это вместо того, что вы в настоящее время:

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

И это будет работать так же, используете ли вы PSeq.map или Array.Parallel.map.

Однако ваша настоящая проблема не будет решена. Эту проблему можно сформулировать так: когда достигается желаемая степень параллельной работы, чтобы получить 100% -ное использование ЦП, недостаточно памяти для поддержки процессов.

Вы не видите, как это не будет разрешено? Вы можете обрабатывать вещи последовательно (менее эффективны по ЦП, но эффективны с точки зрения памяти), или вы можете обрабатывать вещи параллельно (более эффективное ЦП, но заканчивается из памяти).

Варианты затем являются:

  1. Изменение степени параллелизма для использования этих функций к чему-то, что не будет дуть память:

    let result = data 
          |> PSeq.withDegreeOfParallelism 2 
          |> PSeq.map (calculationFunc >> someFuncToExtractResults) 
    
  2. Изменение основной логики для calculationFunc >> someFuncToExtractResults, так что это одна функция, которая более эффективна и передает данные в результаты. Не зная подробностей, не просто понять, как это можно сделать. Но внутренне, конечно, может быть возможна какая-то ленивая загрузка.

+0

Оба являются интенсивными, Я не уверен, что вы имеете в виду мою вторую Не могли бы вы подробно рассказать? –

+0

@ AnthonyTruskinger: Я сделал несколько важных обновлений на основе дополнительной информации, которую вы предоставили. Обратите внимание, что вы должны выбрать компромисс где-нибудь, если вы не хотите, чтобы алгоритм был изменен (вы не получите 100% процессор и эффективную память без изменения алгоритма). Если вы можете изменить алгоритм, хорошо, см. Мой ответ. – yamen

3

Array.Parallel.map использует Parallel.For под капотом, а PSeq представляет собой тонкую оболочку вокруг PLINQ. Но причина, по которой они ведут себя по-другому здесь, - это недостаточная рабочая нагрузка для PSeq.iter, когда seq<Calculation> является последовательным и слишком медленным, давая новые результаты.

У меня нет идеи использовать промежуточный сегмент или массив. Пусть data быть входной массив, двигаясь все вычисления в одном месте путь:

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

и

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

Вы избегаете потребляют слишком много памяти и имеют интенсивные вычисления в одном месте, что приводит для повышения эффективности при параллельном выполнении.

Смежные вопросы