簡體   English   中英

Debug和Release之間的結果不同

[英]Different results between Debug and Release

我有一個問題,我的代碼在將調試與發布進行比較時會返回不同的結果。 我檢查了兩種模式都使用/ fp:exact,所以這不應該是問題。 我對此的主要問題是完整的圖像分析(它是一個圖像理解項目)完全是確定性的,其中絕對沒有任何隨機性。

另一個問題是我的發布版本實際上總是返回相同的結果(圖像為23.014),而debug返回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相同的結果。

至少有兩種可能性:

  1. 啟用優化可能會導致編譯器重新排序操作。 與調試模式中執行的順序相比,這可能會在浮點計算中引入較小的差異,在調試模式下不會發生操作重新排序。 也許可以解釋為調試和發布之間的數值差異,但沒有考慮從一個運行在調試模式下接下來的數值差異。
  2. 您的代碼中存在與內存相關的錯誤,例如讀取/寫入數組的邊界,使用未初始化的變量,使用未分配的指針等。嘗試通過內存檢查器(例如優秀的Valgrind)運行它找出這樣的問題。 與內存相關的錯誤可能會導致非確定性行為。

如果您在Windows上,那么Valgrind不可用(可惜),但您可以在這里查看備選列表。

仔細檢查的一件事是所有變量都被初始化。 很多時候未優化的代碼(調試模式)將初始化內存。

要詳細說明我的評論,這段代碼很可能是您問題的根源:

#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

訪問bestHitcValradveneOffset應該由關鍵區域同步:

#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循環的計數器和並行區域內定義的變量之外,所有變量都是共享的,即除非您還應用default(none)子句,否則您的情況下的shared子句不會執行任何操作。

您應該注意的另一件事是,在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;
    }
}

它只存儲在每個線程中給出最佳命中的參數值,然后在並行區域的末尾,它根據最佳值計算radcValveneOffset 現在只有一個關鍵區域,它位於代碼的末尾。 你也可以繞過它,但你必須引入額外的數組。

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

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

可能是浮點相關嗎​​?

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

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

幾乎任何未定義的行為都可以解釋這一點:未初始化的變量,流氓指針,同一對象的多個修改而沒有插入的序列點等等。結果有時無法產生的事實在某種程度上對未初始化的變量有爭議,但它也可能出現指針問題或邊界錯誤。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM