2011-06-12 57 views
8

我正在实现一些适用于大数据(〜250 MB - 1 GB)的算法。为此,我需要一个循环来做一些基准测试。然而,在这个过程中,我了解到F#正在做一些令人讨厌的事情,我希望你们中的一些人能够澄清。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之外运行并在发布模式下运行。

回答

17

此行为的原因是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呢?这很有趣 - ignore函数是一个非常简单的函数,当你使用它时,它是内联的。该声明是let inline ignore _ =()。内联函数时,编译器会声明一些变量(以存储函数的参数)。

因此,另一种方式来解决这个问题是省略ignore写:

Array2D.zeroCreate 10000 10000 
printfn "%d" (GC.GetTotalMemory(true)) 
Array2D.zeroCreate 10000 10000 
printfn "%d" (GC.GetTotalMemory(true)) 
// (...) 

你会得到一些编译器警告,因为表达式的结果是不unit,但它会奏效。但是,使用某些函数并在本地范围内编写代码可能更可靠。

+0

+1谢谢,有趣:)第一个解决方案有效,但忽略忽略在这里没有帮助。尽管如此,我仍然有兴趣找出它为什么会这样做。 – 2011-06-12 17:30:03

+1

有趣......当我用'-O'选项尝试时(帮助优化),它有所帮助。 – 2011-06-12 18:30:08

+0

奇怪。当我在Visual Studio之外运行时,它也在这里工作。但是,现在循环导致了内存不足的异常,但是只有当我先运行“非循环”版本时:/我想我会坚持本地范围。 – 2011-06-13 08:30:05