2012-03-27 107 views
8

我已經將boost的一部分 - ibeta_inv函數編譯到了一個.Net 64位程序集中,它運行良好,直到我開始從多線程調用它。然後它偶爾返回錯誤的結果。Boost數學(ibeta_inv函數)不是線程安全的?

我遵守它使用此代碼(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); 
     } 
    }; 
} 

有沒有人嘗試過呢?

我沒有試過這個.Net,所以我不知道這是不是原因,但我真的不明白爲什麼,因爲它的工作原理很棒單線程。

用法(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:可以看出在下面我的意見 - 我不知道在所有了,這是一個加速的問題。也許這是一些奇怪的PLinq到C++/CLI bug?我很忙,並且會在稍後回覆更多的事實。

+2

文檔說整個boost.math應該是線程安全的,只要您使用內置的浮點類型(正如我所看到的那樣)。也許你應該提交一個bug? http://www.boost.org/doc/libs/release/libs/math/doc/sf_and_dist/html/math_toolkit/main_overview/threads.html – stanwise 2012-03-27 12:08:40

+0

如果沒有其他東西出現,我可以在本機C++中嘗試應用程序,看看問題是否仍然存在。如果是這樣,一個錯誤報告可能是唯一要做的事情,因爲我無法在源代碼中找到任何東西。雖然可惜......它的運行速度是我們目前實現的反向不完全測試函數的兩倍。 – 2012-03-27 12:12:48

+0

有趣!想到兩個想法:(1)三重檢查你已經在多線程模式下建立了提升(當前版本仍然有區別),(2)這個引用來自@stanwise linked文檔:'後一個限制的原因是需要使用結構來初始化符號常量......因爲在這種情況下,需要運行T的構造函數,導致潛在的競爭條件。「我公開懷疑你的代碼是否意外地暴露了這種競爭條件,並且我全心全意地回來stanwise報告這是一個錯誤。 – MrGomez 2012-04-02 22:11:24

回答

2

我碰巧在C++/CLI 64bit項目中封裝了boost的一部分,並且完全按照您的方式在C#中使用它。

所以,我在你的C++類扔在我自己的升壓的包裝,並將此代碼到C#項目:

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; 
     } 
    } 

它只是執行「永遠」。平行結果始終等於順序結果。這裏沒有線程不安全的...

我可以建議你下載一個新的副本升壓並將其包含在一個全新的項目中,並嘗試一下嗎?

我還注意到,你稱你的結果爲「BoostResult」......並在評論中提到了有關「我們當前的實現」的內容。究竟是什麼你比較你的結果再次?你對「正確」的定義是什麼?

+0

Bonkers。我必須爲這個問題寫下道歉。它基於完全錯誤的假設。可以說,對墨菲法律的強大性表示敬意。錯誤在於我試圖加速的生產wcode。 – 2012-04-07 13:59:12

4

Val類是線程安全的,這一點至關重要。

一個簡單的方法來確保這將是使其不變,但我看到你也有BoostResult需要寫。所以這需要是volatile,或者有某種形式的鎖定。

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; 
     } 
    } 
} 

鎖版:(見this question,以確定哪些是最適合你的應用程序)

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; 
      } 
     } 
    } 
} 

如果你覺得600萬個鎖將是緩慢的,只是試試這個:

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

不,這是不正確的。使用第一版 - 我仍然遇到同樣的問題(並且double不能變化,但這是無關緊要的,因爲直到所有計算完成爲止我都沒有讀取它)。要求600萬次,如果我每次都要鎖定 - 我們目前的實現速度會更快。但是:馬王Val結構工程! – 2012-04-03 11:34:54

+0

@danbystrom噢,對不起。已更新易失版本。除非你對你的struct版本感到滿意,否則如果新的volatile版本有效,我會感興趣。你可能不會讀它,但如果它是由主線程編寫的,即使在'新Val'初始化期間,它也可以被我相信的那個線程緩存。 – weston 2012-04-03 12:20:28

+1

我不認爲你有這個不穩定的概念是正確的。它告訴編譯器,編譯器不能生成將變量緩存在寄存器中的代碼 - 因爲它可以隨時更改。它沒有更深刻的「線程安全」測量。至少我不知道...隨時糾正我!而且,不,我不喜歡我的結構解決方案......直到我知道那些瘋狂的人正在發生什麼!但是它鼓勵我相信我能做到這一點......不知何故!不確定如果它不是您的評論,我會嘗試結構的東西! – 2012-04-03 12:30:56