Производительность Repa 3 и правильное использование слова «сейчас»

StackOverflow https://stackoverflow.com//questions/9713378

Вопрос

Здесь есть основной вопрос о монаде, не связанный с Репа, а также несколько вопросов, специфичных для Репы.

Я работаю над библиотекой, используя 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 лучше оптимизировать и сделать код немного понятнее.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top