Las*_*olt 8 .net f# garbage-collection
我正在实现一些适用于大数据(~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
Run Code Online (Sandbox Code Playgroud)
这里的输出如下:
54000
54000
54000
54000
54000
54000
54000
54000
54000
54000
400000000
800000000
1200000000
Out of memory exception
Run Code Online (Sandbox Code Playgroud)
因此,在循环中,F#会丢弃结果,但是当我不在循环中时,F#将保留对"死数据"的引用(我查看了IL,显然类程序获取此数据的字段).为什么?我能解决这个问题吗?
此代码在Visual Studio外部以及发布模式下运行.
Tom*_*cek 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 ()
Run Code Online (Sandbox Code Playgroud)
...但是为什么编译器在你没有使用该值时声明字段ignore呢?这非常有趣 - 该ignore函数是一个非常简单的函数,在您使用它时会内联.声明是let inline ignore _ = ().在内联函数时,编译器声明一些变量(用于存储函数的参数).
所以,另一种解决方法是省略ignore并写:
Array2D.zeroCreate 10000 10000
printfn "%d" (GC.GetTotalMemory(true))
Array2D.zeroCreate 10000 10000
printfn "%d" (GC.GetTotalMemory(true))
// (...)
Run Code Online (Sandbox Code Playgroud)
你会得到一些编译器警告,因为表达式的结果不是unit,但它会起作用.但是,使用某些函数并在本地范围内编写代码可能更可靠.