Repa 3 производительность и правильное использование «сейчас» - PullRequest
15 голосов
/ 15 марта 2012

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

Я работаю над библиотекой, использующей Repa3. У меня проблемы с получением эффективного параллельного кода. Если я заставлю свои функции возвращать отложенные массивы, я получу мучительно медленный код, который очень хорошо масштабируется до 8 ядер. Этот код занимает более 20 ГБ памяти на профилировщик GHC и работает на несколько порядков медленнее, чем базовые распакованные векторы Haskell.

В качестве альтернативы, если я заставлю все мои функции возвращать массивы манифестов без коробок (все еще пытаюсь использовать слияние внутри функций, например, когда я делаю 'map'), я получаю НАМНОГО более быстрый код (все же медленнее, чем использование незаписанных векторов Haskell ), который вообще не масштабируется, и на самом деле имеет тенденцию становиться немного медленнее с большим количеством ядер.

Основываясь на примере кода FFT в Repa-Algorithms, кажется, что правильный подход - всегда возвращать манифестные массивы. Был ли когда-нибудь случай, когда я должен был возвращать отложенные массивы?

Код FFT также широко использует функцию «сейчас». Тем не менее, я получаю сообщение об ошибке типа при попытке использовать его в моем коде:

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 Array U (Z:. Int) r ' 'r' - это переменная жесткого типа, связанная подпись типа для fromArray :: (BaseRing m r, Unbox r, Repr t r) => Arr t r -> CycRingRepa m r в C: \ Users \ crockeea \ Documents \ Code \ LatticeLib \ CycRingRepa.hs: 50: 1 Ожидаемый тип: CycRingRepa m r Фактический тип: CycRingRepa m (Array 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)

Было бы полезно иметь лучшее представление о том, когда целесообразно использовать «сейчас».

Пара других вопросов Repa: Должен ли я явно вызывать computeUnboxedP (как в примере кода FFT), или я должен использовать более общий computeP (потому что часть unbox определяется моим типом данных)? Должен ли я хранить отложенные или явные массивы в типе данных CycRingRepa? В конце концов я также хотел бы, чтобы этот код работал с Haskell Integers. Потребуется ли мне писать новый код, который использует что-то отличное от U-массивов, или я могу написать полиморфный код, который создает U-массивы для типов unbox и некоторый другой массив для целочисленных / коробочных типов?

Я понимаю, что здесь много вопросов, и я ценю любые / все ответы!

Ответы [ 2 ]

8 голосов
/ 18 марта 2012

Вот исходный код для 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

Здесь "extends" генерирует новый массив путем копирования значений по некоторому набору новых измерений. В частности, это означает, что

arrRepl ! (Z :. i :. j :. k) == arrRepl ! (Z :. i :. j' :. k)

К счастью, extend создает задержанный массив, так как было бы бесполезно проходить через все это копирование.

Задержанные массивы также дают возможность слияния, что невозможно, если массив манифестируется.

Наконец, computeUnboxedP это просто computeP со специализированным типом. Явное указание computeUnboxedP может позволить GHC лучше оптимизировать и делает код немного более понятным.

2 голосов
/ 10 апреля 2012

Repa 3.1 больше не требует явного использования now.Все функции параллельных вычислений являются монадическими и автоматически применяют deepSeqArray к своим результатам.Пакет repa-examples также содержит новую реализацию умножения матриц, демонстрирующую их использование.

...