我正在实现一些适用于大数据(~250 MB - 1 GB)的算法。为此,我需要一个循环来进行一些基准测试。然而,在这个过程中我了解到 F# 正在做一些令人讨厌的事情,我希望你们中的一些人能够澄清这一点。
这是我的代码(问题描述如下):
open System
for i = 1 to 10 do
Array2D.zeroCreate 10000 10000 |> ignore
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000 |> ignore
// should force a garbage collection, and GC.Collect() doesn't help either
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000 |> ignore
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000 |> ignore
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000 |> ignore
printfn "%d" (GC.GetTotalMemory(true))
Console.ReadLine() |> ignore
这里的输出将是这样的:
54000
54000
54000
54000
54000
54000
54000
54000
54000
54000
400000000
800000000
1200000000
Out of memory exception
因此,在循环中 F# 会丢弃结果,但是当我不在循环中时,F# 将保留对“死数据”的引用(我已经查看了 IL,显然类 Program 获取了该数据的字段)。为什么?我可以解决这个问题吗?
此代码在 Visual Studio 外部以 Release模式运行。
最佳答案
出现此行为的原因是 F# 编译器在全局范围内的行为与在局部范围内的行为不同。在全局范围内声明的变量将变成静态字段。模块声明是一个静态类,其 let
声明编译为字段/属性/方法。
解决问题的最简单方法是在函数中编写代码:
let main () =
Array2D.zeroCreate 10000 10000 |> ignore
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000 |> ignore
printfn "%d" (GC.GetTotalMemory(true))
// (...)
Console.ReadLine() |> ignore
main ()
...但是当您不使用该值并且只是忽略
它时,为什么编译器会声明字段呢?这非常有趣 - ignore
函数是一个非常简单的函数,当您使用它时它会被内联。声明为let inlineignore _ = ()
。内联函数时,编译器声明一些变量(用于存储函数的参数)。
因此,解决此问题的另一种方法是省略ignore
并编写:
Array2D.zeroCreate 10000 10000
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000
printfn "%d" (GC.GetTotalMemory(true))
// (...)
您会收到一些编译器警告,因为表达式的结果不是 unit
,但它会起作用。但是,使用某些函数并在本地范围内编写代码可能更可靠。
关于.net - F# 编译器使死对象保持事件状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6323128/