我正在实现一些适用于大数据(~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,显然类程序获取此数据的字段)。为什么?我可以解决这个问题吗?
此代码在Visual Studio外部以及发布模式下运行。
答案 0 :(得分:17)
此行为的原因是F#编译器在全局范围内的行为与在本地范围内的行为不同。在全局范围声明的变量将变为静态字段。模块声明是一个静态类,其let
声明编译为fields / properties / methods。
解决问题的最简单方法是在函数中编写代码:
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
呢?这很有趣 - ignore
函数是一个非常简单的函数,在您使用它时会内联。声明是let inline ignore _ = ()
。在内联函数时,编译器声明一些变量(用于存储函数的参数)。
所以,另一种解决方法是省略ignore
并写:
Array2D.zeroCreate 10000 10000
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000
printfn "%d" (GC.GetTotalMemory(true))
// (...)
你会得到一些编译器警告,因为表达式的结果不是unit
,但它会起作用。但是,使用某些函数并在本地范围内编写代码可能更可靠。