15

ここには、Repa とは関係のない基本的なモナドの質問と、いくつかの Repa 固有の質問があります。

Repa3 を使用してライブラリに取り組んでいます。効率的な並列コードの取得に問題があります。関数が遅延配列を返すようにすると、コードが非常に遅くなり、最大 8 コアまで拡張できます。このコードは、GHC プロファイラーごとに 20GB を超えるメモリを使用し、基本的な Haskell のボックス化されていないベクトルよりも数桁遅く実行されます。

あるいは、すべての関数がボックス化されていないマニフェスト配列を返すようにすると (たとえば、「マップ」を実行するときなど、関数内でまだ融合を使用しようとしている)、はるかに高速なコード (Haskell のボックス化されていないベクトルを使用するよりもまだ遅い) が得られます。まったく拡張できず、実際、コアが増えるとわずかに遅くなる傾向があります。

Repa-Algorithms の FFT サンプル コードに基づくと、正しいアプローチは常にマニフェスト配列を返すことです。遅延配列を返す必要がある場合はありますか?

FFT コードでは、'now' 関数も多用されています。ただし、コードで使用しようとすると、型エラーが発生します。

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

コードは「今」なしで正常にコンパイルされます。「今」では、次のエラーが発生します。

タイプ Array U (Z :. Int) と一致しませんでしたr' withr' `r' は fromArray :: (BaseRing mr, Unbox r, Repr tr) => Arr tr -> CycRingRepa mr の型シグネチャによってバインドされた固定型変数です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)

「今」を使用するのが適切な場合について、より良い考えを持っていると役に立ちます。

その他の Repa に関する質問: (FFT サンプル コードのように) computeUnboxedP を明示的に呼び出す必要がありますか、それともより一般的な computeP を使用する必要がありますか (アンボックス部分がデータ型によって推測されるため)。遅延配列またはマニフェスト配列をデータ型 CycRingRepa に格納する必要がありますか? 最終的には、このコードが Haskell Integers でも動作するようにしたいと考えています。これには、U 配列以外のものを使用する新しいコードを作成する必要がありますか? または、unbox 型の U 配列と Integers/boxed 型の他の配列を作成するポリモーフィック コードを作成できますか?

ここには多くの質問があることを認識しています。すべての回答に感謝します。

4

2 に答える 2

8

のソースコードは次の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れていることを確認したいと思います。そうしないと、利用可能な作業が一連のスレッド間で正しく分散されません。これを機能させるには、次のように書く方が良いですxyy

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

ここで、「extend」は、新しい次元のセット全体で値をコピーすることにより、新しい配列を生成します。特に、これは次のことを意味します。

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

ありがたいことに、extendこのすべてのコピーの問題を経験するのはもったいないので、遅延配列を生成します。

遅延アレイは融合の可能性も可能にしますが、これはアレイがマニフェストである場合には不可能です。

最後に、computeUnboxedPちょうどcomputeP特殊なタイプです。明示computeUnboxedP的に指定すると、GHC の最適化が向上し、コードが少しわかりやすくなります。

于 2012-03-18T01:56:01.363 に答える
2

Repa 3.1 では、明示的に を使用する必要がなくなりましたnow。並列計算関数はすべてモナドであり、deepSeqArrayその結果に自動的に適用されます。repa -examplesパッケージには、その使用法を示す行列乗算の新しい実装も含まれています。

于 2012-04-10T03:40:15.137 に答える