В F # я делаю некоторую вычислительно-интенсивную работу. Такие функции, как Array.Parallel.map
, которые используют параллельную библиотеку .Net Task, ускорили мой код экспоненциально для действительно весьма минимальных усилий.
Однако из-за проблем с памятью я переделываю раздел моего кода, чтобы его можно было лениво оценить внутри выражения последовательности (это означает, что я должен хранить и передавать меньше информации). Когда пришло время оценить, я использовал:
// 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
Подробнее:
- Сохранение версии промежуточного массива выполняется быстро (насколько это возможно до краха) менее чем за 10 минут, но использует ~ 70 ГБ оперативной памяти до того, как он сработает (64 ГБ физического, остальные выгружены)
- Версия seq занимает более 34 минут и использует долю оперативной памяти (всего около 30 ГБ).
- Там вычисляется миллиард значений. Следовательно, миллиард удваивается (по 64 бита каждый) = 7,4505806 ГБ. Там более сложные формы данных... и несколько ненужных копий, которые я очищаю, следовательно, в настоящее время массовое использование ОЗУ.
- Да, архитектура невелика, ленивая оценка - первая часть меня, пытающаяся оптимизировать программу и/или доставлять данные в более мелкие куски.
- С меньшим набором данных обе части кода выводят те же результаты.
- @pad, я попробовал то, что вы предложили, PSeq.iter, казалось, работал правильно (все активные ядра), когда он кормился Calculation [], но все еще остается вопрос о RAM (в конечном итоге он разбился).
- как итоговая часть кода, так и часть вычисления интенсифицированы ЦП (в основном из-за больших наборов данных)
- В версии Seq я просто планирую распараллеливать один раз