2016-03-02 11 views
6

von this question Entstanden, habe ich dieses kleine F # -Code (github) Zufallswerte zu erzeugen, gemäß einer Normalverteilung:Stack-Überlauf trotz Position Endaufruf aber nur in 64-Bit-

// val nextSingle : (unit -> float32) 
let nextSingle = 
    let r = System.Random() 
    r.NextDouble >> float32 

// val gauss : (float32 -> float32 -> seq<float32>) 
let gauss mean stdDev = 
    let rec gauss ready = seq { 
     match ready with 
     | Some spare -> 
      yield spare * stdDev + mean 
      yield! gauss None 
     | _ -> 
      let rec loop() = 
       let u = nextSingle() * 2.f - 1.f 
       let v = nextSingle() * 2.f - 1.f 
       let s = pown u 2 + pown v 2 
       if s >= 1.f || s = 0.f then loop() else 
       u, v, s 
      let u, v, s = loop() 
      let mul = (*)(sqrt(-2.f * log s/s)) 
      yield mul u * stdDev + mean 
      yield! mul v |> Some |> gauss 
    } 
    gauss None 

Ich scheint es, dass Dieser sollte sich nur in Tail-Call-Position aufrufen, ergo nie einen StackOverflowException verursachen, wenn TCO aktiviert ist. Aber es tut beim Ausführen 64-Bit. Es nicht beim Ausführen 32-Bit (d. H. "Prefer 32-Bit" Kontrollkästchen in den Projekteinstellungen).

Ich verwende .NET Framework 4.5.2 und F # 4.4.0.0.

Kann jemand erklären, was das Problem verursacht?

+1

Welche .NET-Version verwenden Sie? – Roujo

+0

Ich fand diese Artikel über die Optimierung von Endanrufen [unter Verwendung von .NET 2.0] (https://blogs.msdn.microsoft.com/davbr/2007/06/20/tail-call-jit-conditions/) vs. [using. NET 4.0] (https://blogs.msdn.microsoft.com/clrcodegeneration/2009/05/11/tail-call-improvements-in-net-framework-4/), vielleicht können sie helfen. – Roujo

+0

Auch welche Version von F # benutzt du? Ich benutze F # 4 und beim Debuggen sehe ich keinen Stapel wachsen. Ich zerlegte den IL-Code und es sieht so aus, als ob der Code F # 4 nicht stackoverflow generieren sollte. – FuleSnabel

Antwort

8

Sieht aus wie ein Fehler im Kompilierungsmechanismus des Sequenzausdrucks. Hier ist eine vereinfachte Repro:

let rec loop r = seq { 
    if r > 0 then 
     let rec unused() = unused() 
     yield r 
     yield! loop r 
} 

printfn "%i" (Seq.nth 10000000 (loop 1)) 

Offensichtlich ist die Anwesenheit der nicht verwendeten rekursiven Definition soll nicht beeinflussen, ob dieser einen Stapelüberlauf erzeugt, aber es funktioniert.

+0

Ich habe dies als ein Problem auf [GitHub] (https://github.com/Microsoft/visualfsharp/issues/996) abgelegt. – kvb

+0

War es eine intuitive oder nicht intuitive Argumentation, die zu dem Problem führte? Ich bin interessiert zu wissen, ob es nur eine einfache Problemlösung war, die 'IL'-Inspektion kombiniert mit einer Minimierung, die ich mit der VS Community Edition machen kann, oder hast du etwas mehr genutzt? Ich versuche, meine Fähigkeit zur Lösung von TCO-Problemen zu verbessern. Wenn du das als separate SO Frage willst, werde ich es gerne so machen. Auch von welcher Version des Codes haben Sie angefangen, der GitHub full, GitHub minimal oder das Beispiel in der Frage, wie ich es unabhängig herausfinden möchte. –

+0

@GuyCoder - Meine Argumentation war in etwa so: Über "Tail Calls" zu sprechen, wenn man einen Berechnungsausdruck betrachtet, kann irreführend sein - das gibt es wirklich nicht (siehe zB http://stackoverflow.com/a/6615060/ 82959 für eine verwandte Diskussion, obwohl sie nicht mit _sequence_ -Ausdrücken zusammenhängt. So etwas wie 'Gauss' wird niemals selbst den Stapel überfluten, nur der Code, der es aufruft, könnte es überlaufen lassen. – kvb