简体   繁体   English

我可以抑制F#编译器在IL代码中复制函数吗?

[英]Can I suppress F# compiler making copy of function in IL code?

I want to create a JIT GPU compiler. 我想创建一个JIT GPU编译器。 You give an F# function, and we JIT compile it. 你给了一个F#函数,我们JIT编译它。 The key of JIT compiling is to be able to cache the compiling result. JIT编译的关键是能够缓存编译结果。 I tried to use the MethodInfo as the caching key, but it won't work. 我试图使用MethodInfo作为缓存键,但它不起作用。 It seems that F# compiler will make a copy of the function instead of referencing the origin function. 似乎F#编译器将复制该函数而不是引用origin函数。 Is there a way to suppress this behavior? 有没有办法抑制这种行为?

Here is a test code, ideally, it should be just compiled twice, but it did it 4 times. 这是一个测试代码,理想情况下,它应该只编译两次,但它做了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

Here is the output: 这是输出:

JIT compiling...
Type  : AleaTest.FS.Lab.Experiments+testFSFuncReflection@50
Method: Int32 Invoke(Int32, Int32)

JIT compiling...
Type  : AleaTest.FS.Lab.Experiments+testFSFuncReflection@51-1
Method: Int32 Invoke(Int32, Int32)

JIT compiling...
Type  : AleaTest.FS.Lab.Experiments+testFSFuncReflection@52-2
Method: Int32 Invoke(Int32, Int32)

JIT compiling...
Type  : AleaTest.FS.Lab.Experiments+testFSFuncReflection@53-3
Method: Int32 Invoke(Int32, Int32)

The F# compiler treats your code more as something like this: F#编译器将您的代码更像是这样的:

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

When compiling this, it will generate a new class to represent the function on each of the lines. 在编译时,它将生成一个新类来表示每行的函数。 If you wrote your test as follows: 如果您按如下方式编写测试:

let f = myGpuCode
launchGpu f
launchGpu f

... it would generate just one class (for the one place where the function is referenced) and then share the same type in both of the calls - so this would work. ...它只生成一个类(对于引用该函数的一个地方),然后在两个调用中共享相同的类型 - 所以这将起作用。

In this example, the compiler actually inlines myGpuCode because it is too short, but if you make it more complex, then it generates very simple Invoke function in both of the classes: 在这个例子中,编译器实际上是内联myGpuCode因为它太短,但是如果你把它变得更复杂,那么它会在两个类中生成非常简单的Invoke函数:

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

I'm sure there is a plenty of caveats, but you could just check if the body of the generated class contains the same IL and uses that as your key instead. 我确信有很多警告,但您可以检查生成的类的主体是否包含相同的IL并将其用作您的密钥。 Once you have the Invoke method, you can get the IL body using the following: 拥有Invoke方法后,您可以使用以下方法获取IL正文:

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

This will be the same for both of the classes - ideally, you could also analyze this to figure out if the code is just calling some other method. 这对于两个类都是相同的 - 理想情况下,您还可以分析这个以确定代码是否只是调用其他方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM