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

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