10

F# で静的メンバー制約を学習しようとしています。Tomas Petricek のブログ投稿を読んinlineで、「静的メンバー制約を使用して記述された操作のみを使用する」関数を記述すると、それらの制約を満たすすべての数値型に対して関数が正しく機能することがわかりました。この質問は、C++ テンプレートと同様に機能することを示してinlineいるため、これら 2 つの関数のパフォーマンスの違いは予想していませんでした。

let MultiplyTyped (A : double[,]) (B : double[,]) =
    let rA, cA = (Array2D.length1 A) - 1, (Array2D.length2 A) - 1
    let cB = (Array2D.length2 B) - 1
    let C = Array2D.zeroCreate<double> (Array2D.length1 A) (Array2D.length2 B)
    for i = 0 to rA do
        for k = 0 to cA do
            for j = 0 to cB do
                C.[i,j] <- C.[i,j] + A.[i,k] * B.[k,j]
    C

let inline MultiplyGeneric (A : 'T[,]) (B : 'T[,]) =
    let rA, cA = Array2D.length1 A - 1, Array2D.length2 A - 1
    let cB = Array2D.length2 B - 1
    let C = Array2D.zeroCreate<'T> (Array2D.length1 A) (Array2D.length2 B)
    for i = 0 to rA do
        for k = 0 to cA do
            for j = 0 to cB do
                C.[i,j] <- C.[i,j] + A.[i,k] * B.[k,j]
    C

それにもかかわらず、2 つの 1024 x 1024 行列を乗算MultiplyTypedするには、私のマシンでは平均 2550 ミリ秒で完了しMultiplyGenericますが、約 5150 ミリ秒かかります。私は当初zeroCreate、それが一般的なバージョンに問題があると思っていましたが、その行を以下の行に変更しても違いはありませんでした.

let C = Array2D.init<'T> (Array2D.length1 A) (Array2D.length2 B) (fun i j -> LanguagePrimitives.GenericZero)

MultiplyGenericと同じように実行するためにここに欠けているものはありMultiplyTypedますか? それともこれは予想されますか?

編集: これは VS2010、F# 2.0、Win7 64 ビット、リリース ビルドであることに注意してください。プラットフォーム ターゲットは x64 (より大きなマトリックスをテストするため) です。これが違いを生みます。x86 は、2 つの関数に対して同様の結果を生成します。

おまけの質問: 推論される型MultiplyGenericは次のとおりです。

val inline MultiplyGeneric :
   ^T [,] ->  ^T [,] ->  ^T [,]
    when ( ^T or  ^a) : (static member ( + ) :  ^T *  ^a ->  ^T) and
          ^T : (static member ( * ) :  ^T *  ^T ->  ^a)

^aタイプはどこから来たのですか?

編集2:これが私のテストコードです:

let r = new System.Random()
let A = Array2D.init 1024 1024 (fun i j -> r.NextDouble())
let B = Array2D.init 1024 1024 (fun i j -> r.NextDouble())

let test f =
    let sw = System.Diagnostics.Stopwatch.StartNew()
    f() |> ignore
    sw.Stop()
    printfn "%A" sw.ElapsedMilliseconds

for i = 1 to 5 do
    test (fun () -> MultiplyTyped A B)

for i = 1 to 5 do
    test (fun () -> MultiplyGeneric A B)
4

2 に答える 2

3

良い質問。最初に簡単な部分に答えます。これ^aは、自然な一般化プロセスの一部にすぎません。次のようなタイプがあると想像してください。

type T = | T with
    static member (+)(T, i:int) = T
    static member (*)(T, T) = 0

その後MultiplyGeneric、このタイプの配列で関数を引き続き使用できます。 と の要素を乗算するAとsBが得られますintが、それらを の要素に追加してCのタイプの値を取得して に戻すTことができるため、問題ありませんC

パフォーマンスの質問については、申し訳ありませんが、十分な説明がありません。あなたの基本的な理解は正しいです。引数を使用MultiplyGenericするdouble[,]ことは、を使用することと同等でなければなりませんMultiplyTyped。ildasm を使用して IL を確認すると、コンパイラは次の F# コードに対して生成します。

let arr = Array2D.zeroCreate 1024 1024
let f1 = MultiplyTyped arr
let f2 = MultiplyGeneric arr

let timer = System.Diagnostics.Stopwatch()
timer.Start()

f1 arr |> ignore

printfn "%A" timer.Elapsed
timer.Restart()

f2 arr |> ignore

printfn "%A" timer.Elapsed

次に、コンパイラが実際にそれらのそれぞれに対して同一のコードを生成し、インライン化MultipyGenericされたコードを内部静的関数に入れることがわかります。生成されたコードに見られる唯一の違いは、ローカルの名前です。コマンド ラインから実行すると、ほぼ同じ経過時間が得られます。ただし、FSI から実行すると、報告されたものと同様の違いが見られます。

なぜそうなるのかは私には明らかではありません。私が見ているように、2つの可能性があります:

  1. FSI のコード生成は、静的コンパイラとは少し異なることを行っている可能性があります
  2. CLR の JIT コンパイラは、実行時に生成されたコードを、コンパイルされたコードとは少し異なる方法で処理する場合があります。たとえば、上記のコードで述べたように、MultiplyGeneric実際に使用すると、インライン化された本文を含む内部メソッドが生成されます。おそらく、CLR の JIT は、パブリック メソッドと内部メソッドの違いを、実行時に生成されるときと、静的にコンパイルされたコード内にあるときとでは異なる方法で処理します。
于 2013-02-21T17:10:25.333 に答える
3

あなたのベンチマークを見てみたいです。同じ結果が得られません (VS 2012 F# 3.0 Win 7 64 ビット)。

let m = Array2D.init 1024 1024 (fun i j -> float i * float j)

let test f =
  let sw = System.Diagnostics.Stopwatch.StartNew()
  f() |> ignore
  sw.Stop()
  printfn "%A" sw.Elapsed

test (fun () -> MultiplyTyped m m)
> 00:00:09.6013188

test (fun () -> MultiplyGeneric m m)
> 00:00:09.1686885

Reflector で逆コンパイルすると、関数は同じに見えます。

最後の質問に関しては、最も制限の少ない制約が推測されます。この行で

C.[i,j] <- C.[i,j] + A.[i,k] * B.[k,j]

の結果の型A.[i,k] * B.[k,j]は指定されておらず、すぐに に渡されるため(+)、余分な型が含まれる可能性があります。制約を強化したい場合は、その行を次のように置き換えることができます

let temp : 'T = A.[i,k] * B.[k,j]
C.[i,j] <- C.[i,j] + temp

それは署名をに変更します

val inline MultiplyGeneric :
  A: ^T [,] -> B: ^T [,] ->  ^T [,]
    when  ^T : (static member ( * ) :  ^T *  ^T ->  ^T) and
          ^T : (static member ( + ) :  ^T *  ^T ->  ^T)

編集

テストを使用すると、出力は次のようになります。

//MultiplyTyped
00:00:09.9904615
00:00:09.5489653
00:00:10.0562346
00:00:09.7023183
00:00:09.5123992
//MultiplyGeneric
00:00:09.1320273
00:00:08.8195283
00:00:08.8523408
00:00:09.2496603
00:00:09.2950196

ideone での同じテストを次に示します(制限時間内に収まるようにいくつかの小さな変更を加えています: 512x512 マトリックスと 1 回のテスト反復)。F# 2.0 を実行し、同様の結果を生成しました。

于 2013-02-21T16:39:39.333 に答える