2016-09-23 5 views
3

JIT GPUコンパイラを作成したいと思います。あなたはF#関数を与え、それをJITコンパイルします。 JITコンパイルの鍵は、コンパイル結果をキャッシュすることです。キャッシングキーとしてMethodInfoを使用しようとしましたが、動作しません。 F#コンパイラは、元の関数を参照するのではなく、関数のコピーを作成するようです。この動作を抑制する方法はありますか?ILコードで関数のコピーを作成するF#コンパイラを抑制できますか?

ここではテストコードですが、理想的には2回だけコンパイルする必要がありますが、それは4回でした。ここで

let compileGpuCode (m:MethodInfo) = 
    printfn "JIT compiling..." 
    printfn "Type : %A" m.ReflectedType 
    printfn "Method: %A" m 
    printfn "" 
    "fake gpu code" 

let gpuCodeCache = ConcurrentDictionary<MethodInfo, string>() 

let launchGpu (func:int -> int -> int) = 
    let m = func.GetType().GetMethod("Invoke", [| typeof<int>; typeof<int> |]) 
    let gpuCode = gpuCodeCache.GetOrAdd(m, compileGpuCode) 
    // launch gpuCode 
    () 

let myGpuCode (a:int) (b:int) = a + 2 * b 

[<Test>] 
let testFSFuncReflection() = 
    launchGpu (+) 
    launchGpu (+) 
    launchGpu myGpuCode 
    launchGpu myGpuCode 

が出力されます。

JIT compiling... 
Type : [email protected] 
Method: Int32 Invoke(Int32, Int32) 

JIT compiling... 
Type : [email protected] 
Method: Int32 Invoke(Int32, Int32) 

JIT compiling... 
Type : [email protected] 
Method: Int32 Invoke(Int32, Int32) 

JIT compiling... 
Type : [email protected] 
Method: Int32 Invoke(Int32, Int32) 
+4

あなたが 'myGpuCode'を渡すたびに、新しい委譲を作成します。それらのそれぞれは、基礎となる関数を呼び出す別の関数(クロージャ)です。私が知る限り、入力関数のアイデンティティを保持する簡単な方法はありません。 – Luaan

+0

@Luaanカレー関数を作成すると、新しいデリゲート(クロージャ)を作成するのが妥当であると思います。しかし、関数を参照しているだけの場合は、新しいコピーを作成するのではなく、参照するための最適化が必要です。 C#では、デリゲート型は元のメソッドを参照しますが、もちろんC#のラムダ関数も、クロージャの新しいメソッドを作成します。 –

+0

C#では、基になるメソッドをラップするデリゲートを取得します。しかしそれは保証ではなく、あなたが閉鎖しているときでもアイデンティティは保持されません。あなたがC#で類似のコードを使用した場合、 'launchGpu(()=> myGpuCode(...))'を同じクラスに入れておくと動作しますが、2つのクラスを使用するとすぐに中断しますラップされた関数を呼び出す2つの別個のメソッドを再度取得します)。私はあなたが未定義の領域に深くいるのではないかと心配しています。解決策が見つかると、F#コンパイラまたは.NETランタイムの変更により将来的には破損する可能性があります。 – Luaan

答えて

3

F#コンパイラは、このようなものとして、より多くのコードを扱います:

launchGpu (fun a b -> myGpuCode a b) 
launchGpu (fun a b -> myGpuCode a b) 

これをコンパイルすると、それが表現するために新しいクラスを生成します各行の関数。次のように、あなたのテストを書いた場合:

let f = myGpuCode 
launchGpu f 
launchGpu f 

...それは(関数が参照されている一つの場所のために)ただ1つのクラスを生成し、通話の両方で同じ型を共有する - これは希望作業。それはあまりにも短いですが、あなたはそれをより複雑にするならば、それはクラスの両方で非常に単純なInvoke関数を生成するため、この例では

、コンパイラが実際にmyGpuCodeをインライン化:

ldarg.1 
ldarg.2 
call int32 Test::myGpuCode(int32, int32) 
ret 

私は注意が必要ですが、生成されたクラスの本体に同じILが含まれているかどうかを確認して、キーとして使用します。あなたがInvokeメソッドを持っていたら、次を使用してIL本体を得ることができます。

let m = func.GetType().GetMethod("Invoke", [| typeof<int>; typeof<int> |]) 
let body = m.GetMethodBody().GetILAsByteArray() 

これは、クラスの両方で同じになります - コードだけであれば、理想的に、あなたも把握し、これを分析することができ他の方法を呼び出す。

関連する問題