2011-07-20 17 views
7

私はHaskellで書かれたプログラムの驚くべき性能について多くのことを聞き、いくつかのテストをしたかったのです。だから、純粋なC言語で書かれたものと性能を比較するだけの行列演算のライブラリを書いた。 まず、500000行列の乗算性能をテストしたところ、それは終わりがないことに気づいた。メモリ不足で10分後に例外が発生しました)!私は、怠惰を取り除くために管理したhaskellをもう少し調べた後、私が得た最良の結果は、Cでの同等のものより約20倍遅くなっています。 質問:以下のコードを見直して、もう少し改善されるでしょうか? 20回もまだ私は少し失望しています。haskell行列実装の性能

import Prelude hiding (foldr, foldl, product) 
import Data.Monoid 
import Data.Foldable 

import Text.Printf 
import System.CPUTime 

import System.Environment 

data Vector a = Vec3 a a a 
       | Vec4 a a a a 
       deriving Show 

instance Foldable Vector where 
    foldMap f (Vec3 a b c) = f a `mappend` f b `mappend` f c 
    foldMap f (Vec4 a b c d) = f a `mappend` f b `mappend` f c `mappend` f d 

data Matr a = Matr !a !a !a !a 
        !a !a !a !a 
        !a !a !a !a 
        !a !a !a !a 

instance Show a => Show (Matr a) where 
    show m = foldr f [] $ matrRows m 
      where f a b = show a ++ "\n" ++ b 

matrCols (Matr a0 b0 c0 d0 a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3) 
       = [Vec4 a0 a1 a2 a3, Vec4 b0 b1 b2 b3, Vec4 c0 c1 c2 c3, Vec4 d0 d1 d2 d3] 

matrRows (Matr a0 b0 c0 d0 a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3) 
       = [Vec4 a0 b0 c0 d0, Vec4 a1 b1 c1 d1, Vec4 a2 b2 c2 d2, Vec4 a3 b3 c3 d3] 

matrFromList [a0, b0, c0, d0, a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3] 
     = Matr a0 b0 c0 d0 
       a1 b1 c1 d1 
       a2 b2 c2 d2 
       a3 b3 c3 d3 

matrId :: Matr Double 
matrId = Matr 1 0 0 0 
       0 1 0 0 
       0 0 1 0 
       0 0 0 1 

normalise (Vec4 x y z w) = Vec4 (x/w) (y/w) (z/w) 1 

mult a b = matrFromList [f r c | r <- matrRows a, c <- matrCols b] where 
      f a b = foldr (+) 0 $ zipWith (*) (toList a) (toList b) 
+4

あなたの行列 'mult'は戻って行列に行列、リストとの間で表現を変えています。これにより、2行のコードで計算を行うことができますが、非常に遅くなります。 –

+2

@stephen tetley:必ずしもそうではありません。コンパイラは最近、インライン展開と森林破壊の面でかなり優れているため、スマートコンパイラは中間リストを削除します。私のテストでは、この実装は、「高速」手書きの展開されていない乗算と比べて、非常に遅くはありません(わずか1.5倍の遅さです)。 UPD:私の間違いは、実際には倍の行列に対して6倍遅くなります。 –

+1

トロールのラベルを付ける危険があるので、私はあなたがこれらのテストで探しているような結果を見つけることはないと思います。 Haskellの優れた点は、Cに比べて読みやすさと表現力が高く、 'STM'や' parallel'などの高度なライブラリであり、ほとんどの場合に比べて並列化が非常に簡単になります他の言語。純粋な数値作業のために、HaskellのパフォーマンスがCの実装に勝ることは非常に困難です。 –

答えて

8

まず、この実装であなたは恒星のパフォーマンスを得ることはできません。異なる表現間で変換が多すぎます。 vectorパッケージのようなものに基づいてコードを作成する方が良いでしょう。また、すべてのテストコードを提供するわけではありませんので、おそらく他の問題があります。これは、生産への生産のパイプラインがハスケルのパフォーマンスに大きな影響を及ぼし、どちらのエンドも提供していないためです。

次に、2つの特定の問題:

1)あなたのベクトルが3または4要素のベクトルとして定義されます。これは、すべてのベクトルに対して、使用されている要素の数を確認する余分なチェックがあることを意味します。 Cでは、あなたの実装がおそらくもっと近いと思います。

struct vec { 
    double *vec; 
    int length; 
} 

あなたはHaskellで同様のことをする必要があります。これは例えばvectorbytestringが実装されている方法です。

Vectorの定義を変更しない場合でも、フィールドを厳密にしてください。 UNPACKプラグマを(VectorとMatrixに)追加するか、-funbox-strict-fieldsでコンパイルする必要があります。

2)foldl'の変更mult

mult a b = matrFromList [f r c | r <- matrRows a, c <- matrCols b] where 
      f a b = Data.List.foldl' (+) 0 $ zipWith (*) (toList a) (toList b) 

に余分な厳しさは foldrよりも、この場合、はるかに優れたパフォーマンスを提供します。

この変更だけで大きな違いが生じるかもしれませんが、残りのコードを見ることなく、言うことは難しいです。ちょうど私が昨日得た新しい結果を共有するために自分の質問に答える

+0

おそらく配列データ型がより効果的です。また、パフォーマンスを向上させるために、アンボックス(UArray)などがあります。 – Nybble

+1

@Wu Xingbo - UArrayを使用するとほぼ確実に改善されます。 'repa'はまだ良いでしょう。 –

+3

'hmatrix'パッケージは、純粋に機能的な設定でBLASとLAPACKへのバインディングを提供します。データ表現は、 'vector'パッケージからの' Data.Vector.Storable'のものです。このパッケージはCと匹敵し、Haskellがもたらした利点が追加されています。 – vivian

4

:私は最新バージョンとパフォーマンスにGHCをアップグレード

  1. は(のみ〜7倍に悪化)確かに悪くはないとなりました。

  2. また、私は愚かで簡単な方法で行列を実装しようとしましたが(下のリストを参照)、実際に受け入れられる性能が得られました。

    data Matr a = Matr (a, a, a, a 
            , a, a, a, a 
            , a, a, a, a 
            , a, a, a, a) 
    
    mult (Matr (!a0, !b0, !c0, !d0, 
          !a1, !b1, !c1, !d1, 
          !a2, !b2, !c2, !d2, 
          !a3, !b3, !c3, !d3)) 
        (Matr (!a0', !b0', !c0', !d0', 
          !a1', !b1', !c1', !d1', 
          !a2', !b2', !c2', !d2', 
          !a3', !b3', !c3', !d3')) 
        = Matr (a0'', b0'', c0'', d0'' 
          , a1'', b1'', c1'', d1'' 
          , a2'', b2'', c2'', d2'' 
          , a3'', b3'', c3'', d3'') 
         where a0'' = a0 * a0' + b0 * a1' + c0 * a2' + d0 * a3' 
           b0'' = a0 * b0' + b0 * b1' + c0 * b2' + d0 * b3' 
           c0'' = a0 * c0' + b0 * c1' + c0 * c2' + d0 * c3' 
           d0'' = a0 * d0' + b0 * d1' + c0 * d2' + d0 * d3' 
    
           a1'' = a1 * a0' + b1 * a1' + c1 * a2' + d1 * a3' 
           b1'' = a1 * b0' + b1 * b1' + c1 * b2' + d1 * b3' 
           c1'' = a1 * c0' + b1 * c1' + c1 * c2' + d1 * c3' 
           d1'' = a1 * d0' + b1 * d1' + c1 * d2' + d1 * d3' 
    
           a2'' = a2 * a0' + b2 * a1' + c2 * a2' + d2 * a3' 
           b2'' = a2 * b0' + b2 * b1' + c2 * b2' + d2 * b3' 
           c2'' = a2 * c0' + b2 * c1' + c2 * c2' + d2 * c3' 
           d2'' = a2 * d0' + b2 * d1' + c2 * d2' + d2 * d3' 
    
           a3'' = a3 * a0' + b3 * a1' + c3 * a2' + d3 * a3' 
           b3'' = a3 * b0' + b3 * b1' + c3 * b2' + d3 * b3' 
           c3'' = a3 * c0' + b3 * c1' + c3 * c2' + d3 * c3' 
           d3'' = a3 * d0' + b3 * d1' + c3 * d2' + d3 * d3' 
    
+1

また、Matrixのフィールドを厳密にしてみてください(データ定義では、すべてのaの前に!を付け加えて、関数内のbangを外してから-funbox-strict-fieldsでコンパイルしてください{ - #UNPACK# - }プラグマ)。実際に使用しているタイプに手作業で{ - #SPECIALIZE# - }することは試してみる価値があるかもしれません。ハスケルはCと頭を接して出てくるはずです。 – barsoap

+0

行列データコンストラクタのパラメータはタプルであり、それにバングを追加すると、その頭だけが評価されます。タプル内にバングを追加することはできますか? GHCはこれを食べていないようだ。だから私はそれをデータコンストラクタに追加しなかったのです。 – Maxym

関連する問題