我可以禁止 F# 编译器在 IL 代码中复制函数吗?
Can I suppress F# compiler making copy of function in IL code?
我想创建一个 JIT GPU 编译器。你给一个 F# 函数,我们 JIT 编译它。 JIT编译的关键是能够缓存编译结果。我尝试使用 MethodInfo
作为缓存键,但它不起作用。似乎 F# 编译器将复制函数而不是引用原始函数。有没有办法抑制这种行为?
这里是测试代码,理想情况下应该只编译两次,结果编译了四次
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)
编译时,它会生成一个新的 class 来表示每一行的函数。如果您按如下方式编写测试:
let f = myGpuCode
launchGpu f
launchGpu f
... 它只会生成一个 class (对于引用函数的地方),然后在两个调用中共享相同的类型 - 所以这会起作用。
在这个例子中,编译器实际上内联了 myGpuCode
因为它太短了,但是如果你让它更复杂,那么它会在 [=29] 中生成非常简单的 Invoke
函数=]es:
ldarg.1
ldarg.2
call int32 Test::myGpuCode(int32, int32)
ret
我敢肯定有很多注意事项,但您可以检查生成的 class 的正文是否包含相同的 IL 并将其用作您的密钥。获得 Invoke
方法后,您可以使用以下方法获取 IL 主体:
let m = func.GetType().GetMethod("Invoke", [| typeof<int>; typeof<int> |])
let body = m.GetMethodBody().GetILAsByteArray()
这两个 classes 都是一样的 - 理想情况下,您还可以分析它以确定代码是否只是调用其他方法。
我想创建一个 JIT GPU 编译器。你给一个 F# 函数,我们 JIT 编译它。 JIT编译的关键是能够缓存编译结果。我尝试使用 MethodInfo
作为缓存键,但它不起作用。似乎 F# 编译器将复制函数而不是引用原始函数。有没有办法抑制这种行为?
这里是测试代码,理想情况下应该只编译两次,结果编译了四次
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)
编译时,它会生成一个新的 class 来表示每一行的函数。如果您按如下方式编写测试:
let f = myGpuCode
launchGpu f
launchGpu f
... 它只会生成一个 class (对于引用函数的地方),然后在两个调用中共享相同的类型 - 所以这会起作用。
在这个例子中,编译器实际上内联了 myGpuCode
因为它太短了,但是如果你让它更复杂,那么它会在 [=29] 中生成非常简单的 Invoke
函数=]es:
ldarg.1
ldarg.2
call int32 Test::myGpuCode(int32, int32)
ret
我敢肯定有很多注意事项,但您可以检查生成的 class 的正文是否包含相同的 IL 并将其用作您的密钥。获得 Invoke
方法后,您可以使用以下方法获取 IL 主体:
let m = func.GetType().GetMethod("Invoke", [| typeof<int>; typeof<int> |])
let body = m.GetMethodBody().GetILAsByteArray()
这两个 classes 都是一样的 - 理想情况下,您还可以分析它以确定代码是否只是调用其他方法。