2012-08-14 88 views
2

我有問題,比較調試和發佈時,我的代碼返回不同的結果。我檢查了兩種模式都使用/ fp:精確,所以不應該是問題。我的主要問題是完整的圖像分析(它是一個圖像理解項目)是完全確定性的,它絕對沒有任何隨機性。調試和發佈之間的不同結果

另一個問題是,我的發佈版本實際上總是返回相同的結果(圖像23.014),而調試返回22和23之間的一些隨機值,這應該不是。我已經檢查過它是否可能與線程相關,但是多線程算法中唯一的部分將返回調試和發佈完全相同的結果。

這裏還會發生什麼?

UPDATE1:的代碼,我現在找到負責此行爲:

float PatternMatcher::GetSADFloatRel(float* sample, float* compared, int sampleX, int compX, int offX) 
{ 
    if (sampleX != compX) 
    { 
     return 50000.0f; 
    } 
    float result = 0; 

    float* pTemp1 = sample; 
    float* pTemp2 = compared + offX; 

    float w1 = 0.0f; 
    float w2 = 0.0f; 
    float w3 = 0.0f; 

    for(int j = 0; j < sampleX; j ++) 
    { 
     w1 += pTemp1[j] * pTemp1[j]; 
     w2 += pTemp1[j] * pTemp2[j]; 
     w3 += pTemp2[j] * pTemp2[j]; 
    }    
    float a = w2/w3; 
    result = w3 * a * a - 2 * w2 * a + w1; 
    return result/sampleX; 
} 

UPDATE2: 這是不可再生與32位的代碼。雖然調試和發佈代碼總是會導致32位的值相同,但它仍然不同於64位版本,並且64位調試仍會返回一些完全隨機的值。

Update3: 好吧,我發現它肯定是由OpenMP造成的。當我禁用它時,它工作正常。 (Debug和Release都使用相同的代碼,並且都啓用了OpenMP)。

以下是代碼給我找麻煩:

#pragma omp parallel for shared(last, bestHit, cVal, rad, veneOffset) 
for(int r = 0; r < 53; ++r) 
{ 
    for(int k = 0; k < 3; ++k) 
    { 
     for(int c = 0; c < 30; ++c) 
     { 
      for(int o = -1; o <= 1; ++o) 
      { 
       /* 
       r: 2.0f - 15.0f, in 53 steps, representing the radius of blood vessel 
       c: 0-29, in steps of 1, representing the absorption value (collagene) 
       iO: 0-2, depending on current radius. Signifies a subpixel offset (-1/3, 0, 1/3) 
       o: since we are not sure we hit the middle, move -1 to 1 pixels along the samples 
       */ 

       int offset = r * 3 * 61 * 30 + k * 30 * 61 + c * 61 + o + (61 - (4*w+1))/2; 

       if(offset < 0 || offset == fSamples.size()) 
       { 
        continue; 
       } 
       last = GetSADFloatRel(adapted, &fSamples.at(offset), 4*w+1, 4*w+1, 0); 
       if(bestHit > last) 
       { 
        bestHit = last; 
        rad = (r+8)*0.25f; 
        cVal = c * 2; 
        veneOffset =(-0.5f + (1.0f/3.0f) * k + (1.0f/3.0f)/2.0f); 
        if(fabs(veneOffset) < 0.001) 
         veneOffset = 0.0f; 
       } 
       last = GetSADFloatRel(input, &fSamples.at(offset), w * 4 + 1, w * 4 + 1, 0); 
       if(bestHit > last) 
       { 
        bestHit = last; 
        rad = (r+8)*0.25f; 
        cVal = c * 2; 
        veneOffset = (-0.5f + (1.0f/3.0f) * k + (1.0f/3.0f)/2.0f); 
        if(fabs(veneOffset) < 0.001) 
         veneOffset = 0.0f; 
       } 
      } 
     } 
    } 
} 

注:與發行模式和OpenMP激活我得到相同的結果與停用的OpenMP。調試模式和OpenMP激活得到不同的結果,OpenMP停用獲得與Release相同的結果。

+0

如果我們看到一些代碼,我們可能會提供更多幫助。一般來說,我的猜測是,你在正常編譯器正確理解的地方使用了鬆散的語法,但是調試器卻沒有。 – rsegal 2012-08-14 14:16:42

+1

使用valgrind檢查是否有可能導致非確定性行爲的內存損壞。 – 2012-08-14 14:17:06

+1

有趣。通常的Heisenbug情況是調試獲得更可靠的結果。 – dmckee 2012-08-14 14:19:09

回答

3

要闡述我的意見,這是最有可能是你的問題的根源代碼:

#pragma omp parallel for shared(last, bestHit, cVal, rad, veneOffset) 
{ 
    ... 
    last = GetSADFloatRel(adapted, &fSamples.at(offset), 4*w+1, 4*w+1, 0); 
    if(bestHit > last) 
    { 

last只分配給它再次讀取之前,所以這是一個很好的候選人,作爲一個lastprivate變量,如果你真的需要來自並行區域之外的最後一次迭代的值。否則,只需使它private

訪問bestHitcValrad,和veneOffset應由一個臨界區域同步:

#pragma omp critical 
if (bestHit > last) 
{ 
    bestHit = last; 
    rad = (r+8)*0.25f; 
    cVal = c * 2; 
    veneOffset =(-0.5f + (1.0f/3.0f) * k + (1.0f/3.0f)/2.0f); 
    if(fabs(veneOffset) < 0.001) 
     veneOffset = 0.0f; 
} 

注意,默認情況下所有的變量,除了parallel for循環的計數器和那些並行區域內所定義,是共享的,即您的案例中的shared子句不會執行任何操作,除非您也應用default(none)子句。

另一件您應該注意的事情是,在32位模式下,Visual Studio使用x87 FPU數學運算,而在64位模式下它默認使用SSE數學運算。 x87 FPU使用80位浮點精度進行中間計算(即使僅涉及float的計算),而SSE單元僅支持標準IEEE單精度和雙精度。將OpenMP或任何其他並行化技術引入到32位x87 FPU代碼意味着在某些點,中間值應該被轉換回單精度float,並且如果進行了足夠多次的輕微或顯着差異(取決於數值穩定性該算法)可以在串行代碼和並行代碼的結果之間觀察到。

基於您的代碼,我建議以下修改後的代碼會給你很好的並行性能,因爲在每次迭代不同步:

#pragma omp parallel private(last) 
{ 
    int rBest = 0, kBest = 0, cBest = 0; 
    float myBestHit = bestHit; 

    #pragma omp for 
    for(int r = 0; r < 53; ++r) 
    { 
     for(int k = 0; k < 3; ++k) 
     { 
      for(int c = 0; c < 30; ++c) 
      { 
       for(int o = -1; o <= 1; ++o) 
       { 
        /* 
        r: 2.0f - 15.0f, in 53 steps, representing the radius of blood vessel 
        c: 0-29, in steps of 1, representing the absorption value (collagene) 
        iO: 0-2, depending on current radius. Signifies a subpixel offset (-1/3, 0, 1/3) 
        o: since we are not sure we hit the middle, move -1 to 1 pixels along the samples 
        */ 

        int offset = r * 3 * 61 * 30 + k * 30 * 61 + c * 61 + o + (61 - (4*w+1))/2; 

        if(offset < 0 || offset == fSamples.size()) 
        { 
         continue; 
        } 
        last = GetSADFloatRel(adapted, &fSamples.at(offset), 4*w+1, 4*w+1, 0); 
        if(myBestHit > last) 
        { 
         myBestHit = last; 
         rBest = r; 
         cBest = c; 
         kBest = k; 
        } 
        last = GetSADFloatRel(input, &fSamples.at(offset), w * 4 + 1, w * 4 + 1, 0); 
        if(myBestHit > last) 
        { 
         myBestHit = last; 
         rBest = r; 
         cBest = c; 
         kBest = k; 
        } 
       } 
      } 
     } 
    } 
    #pragma omp critical 
    if (bestHit > myBestHit) 
    { 
     bestHit = myBestHit; 
     rad = (rBest+8)*0.25f; 
     cVal = cBest * 2; 
     veneOffset =(-0.5f + (1.0f/3.0f) * kBest + (1.0f/3.0f)/2.0f); 
     if(fabs(veneOffset) < 0.001) 
     veneOffset = 0.0f; 
    } 
} 

只存儲了給該參數的值在每個線程中最好命中,然後在並行區域的末尾,根據最佳值計算rad,cValveneOffset。現在只有一個關鍵區域,它在代碼的末尾。你也可以解決它,但你必須引入額外的數組。

+0

謝謝,最後聲明爲private,現在我在發佈和調試模式之間得到相同的結果! – SinisterMJ 2012-08-15 13:32:53

+0

@AntonRoth,你是否也添加了關鍵部分?沒有它們,你不能保證數據競賽不會發生。 – 2012-08-15 14:13:55

+0

是的,我做了,但是嘗試了20次,它對結果沒有任何影響。實際上,使用#pragma omp critical的性能比首先使用單線程的性能差得多。 – SinisterMJ 2012-08-15 14:25:32

7

至少有兩種可能:

  1. 打開優化可能會導致編譯器重新排序操作。與在調試模式下執行的命令相比,這可以在浮點計算中引入小的差異,在該模式下不會發生操作重新排序。這可能考慮調試和釋放之間的數字差異,但而不是帳戶在調試模式下從一個運行到下一個的數字差異。
  2. 您的代碼中存在與內存有關的錯誤,例如讀取/寫入超出數組邊界,使用未初始化的變量,使用未分配的指針等。嘗試通過內存檢查器(如優秀Valgrind,找出這樣的問題。內存相關的錯誤可能佔非確定性行爲。

如果您在Windows上,則Valgrind不可用(可惜),但您可以查看here以獲取替代方案列表。

+1

我已經在發佈模式下完全關閉了優化,現在我在發佈模式下獲得了相同的隨機結果。爲什麼完全優化會導致確定性的結果,而調試給我一些隨機返回值? – SinisterMJ 2012-08-14 14:29:17

+2

我遇到非確定性行爲時(我不使用隨機數)檢查的第一件事是內存錯誤。在沒有合適的工具的情況下,它們是一種巨大的痛苦(我曾經花費數天的時間在我有適當的內存調試工具之前找到它們)。 – Nathan 2012-08-14 14:32:00

+1

@AntonRoth它通常是相反的,但優化程序可能會消除某些計算,因爲它「知道」結果,在沒有優化的情況下它不會。如果這些計算在某處使用未初始化的值... – 2012-08-14 14:35:49

3

重複檢查的一件事是所有變量都被初始化。多次未優化的代碼(調試模式)會初始化內存。

2

我會說在調試中的變量初始化vs不在發佈。但是你的結果不會支持這一點(可靠的結果)。

您的代碼是否依賴於任何特定的偏移量或大小?調試版本會圍繞某些分配放置警衛字節。

它可能是浮點相關?

調試浮點堆棧與爲提高效率而構建的版本不同。

看吧:http://thetweaker.wordpress.com/2009/08/28/debugrelease-numerical-differences/

2

幾乎任何不確定的行爲可以說明這一點:未初始化變量 ,流氓指針,同一對象 的多個修改而不插入順序點,等等,等等的事實對於 未初始化的變量, 結果有時不可再生,但它也可能出現在指針問題或邊界錯誤。

請注意,優化可以改變結果,特別是在英特爾上。 優化可以更改哪些中間值會溢出到內存中,如果您還沒有仔細使用括號,即使是在表達式中評估 的順序也可以更改爲 。 (衆所周知,在機器浮點上,(a + b) + c) != a + (b + c))。仍然結果應該是確定性的: 根據優化程度 您將得到不同的結果,但對於任何一組優化標誌,您應該得到相同的結果。

相關問題