F#编译器使死对象保持活动状态

时间:2011-06-12 16:38:43

标签: .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

这里的输出如下:

54000
54000
54000
54000
54000
54000
54000
54000
54000
54000
400000000
800000000
1200000000

Out of memory exception

所以,在循环中,F#会丢弃结果,但是当我不在循环中时,F#将保留对“死数据”的引用(我查看了IL,显然类程序获取此数据的字段)。为什么?我可以解决这个问题吗?

此代码在Visual Studio外部以及发布模式下运行。

1 个答案:

答案 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,但它会起作用。但是,使用某些函数并在本地范围内编写代码可能更可靠。