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



我想创建一个JIT GPU编译器。你给出一个f#函数,然后我们对它进行JIT编译。JIT编译的关键是能够缓存编译结果。我试图使用MethodInfo作为缓存键,但它不会工作。看起来f#编译器将复制函数而不是引用原始函数。有没有办法抑制这种行为?

这是一个测试代码,理想情况下,它应该只编译两次,但它做了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  : 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)

f#编译器会像这样处理你的代码:

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

编译时,它将在每一行生成一个新类来表示该函数。如果您这样写您的测试:

let f = myGpuCode
launchGpu f
launchGpu f

…它将只生成一个类(用于函数被引用的一个地方),然后在两次调用中共享相同的类型-所以这是可行的。

在这个例子中,编译器实际上内联了myGpuCode,因为它太短了,但如果你让它更复杂,那么它在两个类中都生成了非常简单的Invoke函数:

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()

这对两个类来说都是一样的-理想情况下,您还可以分析它以确定代码是否只是调用了其他方法。

相关内容

  • 没有找到相关文章

最新更新