Производительность Repa 3 и правильное использование слова «сейчас»
-
14-12-2019 - |
Вопрос
Здесь есть основной вопрос о монаде, не связанный с Репа, а также несколько вопросов, специфичных для Репы.
Я работаю над библиотекой, используя Repa3.У меня возникли проблемы с получением эффективного параллельного кода.Если я заставлю свои функции возвращать задержанные массивы, я получу мучительно медленный код, который очень хорошо масштабируется до 8 ядер.Этот код занимает более 20 ГБ памяти на профилировщик GHC и работает на несколько порядков медленнее, чем базовые распакованные векторы Haskell.
В качестве альтернативы, если я заставлю все свои функции возвращать массивы манифестов в распакованном виде (все еще пытаясь использовать слияние внутри функций, например, когда я делаю «карту»), я получу НАМНОГО более быстрый код (все еще медленнее, чем при использовании неупакованных векторов Haskell), который не вообще не масштабируется и фактически имеет тенденцию становиться немного медленнее с большим количеством ядер.
Судя по примеру кода БПФ в Repa-Algorithms, кажется, что правильный подход — всегда возвращать массивы манифеста.Есть ли когда-нибудь случай, когда мне следует возвращать задержанные массивы?
Код БПФ также широко использует функцию «сейчас».Однако я получаю ошибку типа, когда пытаюсь использовать ее в своем коде:
type Arr t r = Array t DIM1 r
data CycRingRepa m r = CRTBasis (Arr U r)
| PowBasis (Arr U r)
fromArray :: forall m r t. (BaseRing m r, Unbox r, Repr t r) => Arr t r -> CycRingRepa m r
fromArray =
let mval = reflectNum (Proxy::Proxy m)
in \x ->
let sh:.n = extent x
in assert (mval == 2*n) PowBasis $ now $ computeUnboxedP $ bitrev x
Код компилируется нормально без «сейчас».С «сейчас» я получаю следующую ошибку:
Не удалось сопоставить тип
r' with
Массив U(Z:.Int) r '`r' - это жесткая переменная типа, связанная типом подписи для OffArray ::(Базовый MR, Unbox R, repr Tr) => arr tr -> cycringrepa mr at c: users crockeea documents code latticelib cycringrepa.hs: 50: 1 Ожидаемый тип:Cycringrepa MR фактический тип:CycRingRepa m (массив U DIM1 r)
Я не думать этот это моя проблема.Было бы полезно, если бы кто-нибудь объяснил, как работает Монада «сейчас».По моим оценкам, монада создаёт «Arr U (Arr U r)».Я ожидаю «Arr U r», который будет соответствовать шаблону конструктора данных.Что происходит и как это исправить?
Сигнатуры типов:
computeUnboxedP :: Fill r1 U sh e => Array r1 sh e -> Array U sh e
now :: (Shape sh, Repr r e, Monad m) => Array r sh e -> m (Array r sh e)
Было бы полезно иметь лучшее представление о том, когда уместно использовать слово «сейчас».
Еще пара вопросов по Репе:Должен ли я явно вызывать ComputeUnboxedP (как в примере кода БПФ) или мне следует использовать более общий метод ComputeP (поскольку часть распаковки определяется моим типом данных)?Должен ли я хранить массивы с задержкой или манифеста в типе данных CycRingRepa?В конце концов я также хотел бы, чтобы этот код работал с целыми числами Haskell.Потребуется ли для этого написать новый код, который использует что-то иное, чем массивы U, или я могу написать полиморфный код, который создает массивы U для типов распаковки и какой-то другой массив для целых/упаковочных типов?
Я понимаю, что здесь много вопросов, и я ценю любые ответы!
Решение
Repa 3.1 больше не требует явного использования now
.Все функции параллельных вычислений являются монадическими и автоматически применяются. deepSeqArray
своим результатам.А примеры повторов Пакет также содержит новую реализацию умножения матриц, демонстрирующую их использование.
Другие советы
Вот исходный код для now
:
now arr = do
arr `deepSeqArray` return ()
return arr
Так что на самом деле это просто монадическая версия deepSeqArray
.Вы можете использовать любой из них для принудительной оценки, а не для того, чтобы зацикливаться на мысли.Эта «оценка» отличается от «вычислений», вынужденных, когда computeP
называется.
В вашем коде now
неприменимо, поскольку вы не находитесь в монаде.Но в этом контексте deepSeqArray
тоже не помогло бы.Рассмотрим такую ситуацию:
x :: Array U Int Double
x = ...
y :: Array U Int Double
y = computeUnboxedP $ map f x
С y
относится к x
, мы хотели бы быть уверены x
вычисляется до начала вычислений y
.В противном случае доступная работа не будет правильно распределена между группами потоков.Чтобы это получилось, лучше написать y
как
y = deepSeqArray x . computeUnboxedP $ map f x
Теперь для задержанного массива мы имеем
deepSeqArray (ADelayed sh f) y = sh `deepSeq` f `seq` y
Вместо вычисления всех элементов это просто гарантирует, что форма вычислена, и уменьшает f
до слабоголовой нормальной формы.
Что касается массивов манифеста и массивов с задержкой, то, безусловно, массивы с задержкой по времени предпочтительнее.
multiplyMM arr brr
= [arr, brr] `deepSeqArrays`
A.sumP (A.zipWith (*) arrRepl brrRepl)
where trr = computeUnboxedP $ transpose2D brr
arrRepl = trr `deepSeqArray` A.extend (Z :. All :. colsB :. All) arr
brrRepl = trr `deepSeqArray` A.extend (Z :. rowsA :. All :. All) trr
(Z :. _ :. rowsA) = extent arr
(Z :. colsB :. _ ) = extent brr
Здесь «расширить» генерирует новый массив, копируя значения в некоторый набор новых измерений.В частности, это означает, что
arrRepl ! (Z :. i :. j :. k) == arrRepl ! (Z :. i :. j' :. k)
К счастью, extend
создает отложенный массив, поскольку было бы напрасной тратой времени на все это копирование.
Массивы с задержкой также допускают возможность слияния, что невозможно, если массив является явным.
Окончательно, computeUnboxedP
просто computeP
со специализированным типом.предоставление computeUnboxedP
явно может позволить GHC лучше оптимизировать и сделать код немного понятнее.