2012-03-27 6 views
8

Ich habe einen Teil von Boost - die ibeta_inv-Funktion - in eine .Net 64-Bit-Assembly kompiliert und es funktionierte großartig, bis ich anfing, es aus mehreren Threads aufzurufen. Dann gibt es gelegentlich falsche Ergebnisse zurück.Boost Mathematik (ibeta_inv Funktion) nicht thread sicher?

I erfüllt es mit diesem Code (C++/CLI):

// Boost.h 

#pragma once 

#include <boost/math/special_functions/beta.hpp> 

using namespace boost::math; 

namespace Boost { 

    public ref class BoostMath 
    { 
    public: 
     double static InverseIncompleteBeta(double a, double b, double x) 
     { 
      return ibeta_inv(a,b,x); 
     } 
    }; 
} 

Hat jemand schon einmal ausprobiert?

Ich habe nicht versucht, dies außerhalb .Net, so dass ich nicht weiß, ob dies die Ursache ist, aber ich sehe wirklich nicht warum, da es funktioniert super single threaded.

Verbrauch (C#):

private void calcBoost(List<Val> vals) 
{ 
    //gives WRONG results (sometimes): 
    vals.AsParallel().ForAll(v => v.BoostResult = BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 
    //gives CORRECT results: 
    vals.ForEach(v => v.BoostResult = BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 
} 

UPDATE: Wie unten in meinen Kommentaren zu sehen - ich bin nicht sicher, überhaupt nicht mehr, dass dies ein Boost-Problem. Vielleicht ist es ein seltsamer PLinq zu C++/CLI Bug ??? Ich bin geblufft und werde später mit mehr Fakten zurückkehren.

+2

Dokumentation sagt ganze boost.math sollte threadsicher sein, solange Sie eingebaute Fließkommatypen verwenden (was, wie ich sehe, Sie tun). Vielleicht sollten Sie einen Fehler einreichen? http://www.boost.org/doc/libs/release/libs/math/doc/sf_and_dist/html/math_toolkit/main_overview/threads.html – stanwise

+0

Wenn nichts anderes auftaucht, kann ich es in einem nativen C++ ausprobieren App und sehen, ob das Problem bestehen bleibt. Wenn dies der Fall ist, kann ein Fehlerbericht die einzige Sache sein, da ich nichts im Quellcode finden kann. Schade, aber ... es läuft doppelt so schnell wie unsere aktuelle Implementierung der inversen unvollständigen Beta-Funktion. –

+0

Interessant!Zwei Gedanken kommen in den Sinn: (1) Treble-Check, den Sie Boost im Multithread-Modus gebaut haben (aktuelle Versionen machen immer noch die Unterscheidung), und (2) dieses Zitat aus der Dokumentation @stanwise verknüpft: 'Der Grund für die letztere Einschränkung ist die Notwendigkeit, symbolische Konstanten mit Hilfe von Konstrukten zu initialisieren ... da in diesem Fall die Konstruktoren von T ausgeführt werden müssen, was zu möglichen Wettlaufbedingungen führt. Ich frage mich offen, ob dein Code diesen Wettlaufzustand unerwartet offen legt und ich von ganzem Herzen zurück stanwise, dies als Fehler zu melden. – MrGomez

Antwort

2

Ich habe einen Teil der Boost in einem C++/CLI 64bit-Projekt gekapselt und benutze es genau wie du aus C#.

Also warf ich in C++ Klasse in meinem eigenen Boost-Wrapper und fügte diesen Code zu C# Projekt:

private class Val 
    { 
     public double A; 
     public double B; 
     public double X; 
     public double ParallellResult; 
    } 

    private static void findParallelError() 
    { 
     var r = new Random(); 

     while (true) 
     { 
      var vals = new List<Val>(); 
      for (var i = 0; i < 1000*1000; i++) 
      { 
       var val = new Val(); 
       val.A = r.NextDouble()*100; 
       val.B = val.A + r.NextDouble()*1000; 
       val.X = r.NextDouble(); 
       vals.Add(val); 
      } 

      // parallel calculation 
      vals.AsParallel().ForAll(v => v.ParallellResult = Boost.BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 

      /sequential verification 
      var error = vals.Exists(v => v.ParallellResult != Boost.BoostMath.InverseIncompleteBeta(v.A, v.B, v.X)); 
      if (error) 
       return; 
     } 
    } 

Und es führt nur "für immer". Die parallelen Ergebnisse sind zu jeder Zeit den sequentiellen Ergebnissen gleich. Keine Threadunsicherheit hier ...

Darf ich vorschlagen, dass Sie eine neue Kopie von Boost herunterladen und in ein komplett neues Projekt einbinden und das ausprobieren?

Ich merke auch, dass Sie Ihr Ergebnis "BoostResult" aufgerufen haben ... und erwähnen Sie in den Kommentaren etwas über "unsere aktuelle Implementierung". Was genau vergleichen Sie Ihr Ergebnis gegen? Was ist deine Definition von "korrekt"?

+0

Bonkers. Ich werde eine Entschuldigung für diese Frage aufschreiben müssen. Es basiert auf völlig falschen Annahmen. Ein Tribut an die Mächtigkeit des Murphys Law könnte man sagen. Der Fehler ist in der Produktion wcode, die ich versuchte zu beschleunigen. –

4

Es ist wichtig, dass die Val Klasse threadsafe ist.

Ein einfacher Weg, um sicherzustellen, dass es wäre, es unveränderlich zu machen, aber ich sehe, Sie haben auch BoostResult, die geschrieben werden müssen. Also muss das volatile sein, oder irgendeine Form der Verriegelung haben.

public sealed class Val 
{ 
    // Immutable fields are inheriently threadsafe 
    public readonly double A; 
    public readonly double B; 
    public readonly double X; 

    // volatile is an easy way to make a single field thread safe 
    // box and unbox to allow double as volatile 
    private volatile object boostResult = 0.0; 

    public Val(double A, double B, double X) 
    { 
     this.A = A; 
     this.B = B; 
     this.X = X; 
    } 

    public double BoostResult 
    { 
     get 
     { 
      return (double)boostResult; 
     } 
     set 
     { 
      boostResult = value; 
     } 
    } 
} 

Lock-Version: (this question sehen, um zu bestimmen, welche für Ihre Anwendung am besten geeignet ist)

public sealed class Val 
{ 
    public readonly double A; 
    public readonly double B; 
    public readonly double X; 

    private readonly object lockObject = new object(); 
    private double boostResult; 

    public Val(double A, double B, double X) 
    { 
     this.A = A; 
     this.B = B; 
     this.X = X; 
    } 

    public double BoostResult 
    { 
     get 
     { 
      lock (lockObject) 
      { 
       return boostResult; 
      } 
     } 
     set 
     { 
      lock (lockObject) 
      { 
       boostResult = value; 
      } 
     } 
    } 
} 

Und wenn Sie denken, 6.000.000 Schlösser langsam sein, versuchen Sie einfach dieses:

using System; 

namespace ConsoleApplication17 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      { //without locks 
       var startTime = DateTime.Now; 
       int i2=0; 
       for (int i = 0; i < 6000000; i++) 
       { 
        i2++; 
       } 
       Console.WriteLine(i2); 
       Console.WriteLine(DateTime.Now.Subtract(startTime)); //0.01 seconds on my machine 
      } 
      { //with locks 
       var startTime = DateTime.Now; 
       var obj = new Object(); 
       int i2=0; 
       for (int i = 0; i < 6000000; i++) 
       { 
        lock (obj) 
        { 
         i2++; 
        } 
       } 
       Console.WriteLine(i2); 
       Console.WriteLine(DateTime.Now.Subtract(startTime)); //0.14 seconds on my machine, and this isn't even in parallel. 
      } 
      Console.ReadLine(); 
     } 
    } 
} 
+0

Nein, das ist nicht korrekt. Mit der ersten Version habe ich immer noch das gleiche Problem (und ein Double kann nicht flüchtig sein, aber das ist irrelevant, da ich erst dann lese, wenn alle Berechnungen abgeschlossen sind.) Version zwei kommt nicht in Frage, da ich dies mache Rufen Sie sechs Millionen Mal an, und wenn ich jedes Mal sperren werde, wird unsere aktuelle Implementierung viel schneller sein. ABER: ma König Val a struct WORKS !!! –

+0

@danbystrom Oh ja, tut mir leid. Habe die volatile Version aktualisiert. Wenn Sie mit Ihrer Struct-Version nicht zufrieden sind, würde mich die neue volatile Version interessieren. Du magst es nicht lesen, aber wenn es vom Hauptthread geschrieben wird, auch nur während der Initialisierung eines "neuen Val", könnte es von diesem Thread, den ich glaube, zwischengespeichert werden. – weston

+1

Ich glaube nicht, dass Sie dieses flüchtige Konzept richtig verstanden haben. Es teilt dem Compiler mit, dass der Compiler keinen Code erzeugen kann, der die Variable in einem Register zwischenspeichert - da sie jederzeit geändert werden kann. Es hat keine tiefgründigere "thread safe" Meaing. Zumindest nicht nach meinem Wissen ... Fühl mich frei, mich zu korrigieren! Und nein, ich bin nicht glücklich mit meiner Struct-Lösung ... bis ich weiß, was die bonkers vorhaben !!! Aber es hat mich ermutigt zu glauben, dass ich das irgendwie schaffen kann ... irgendwie! Ich bin mir nicht sicher, ob ich das Struct-Ding ausprobiert hätte, wäre da nicht dein Kommentar gewesen! –