5

デバッグとリリースを比較すると、コードが異なる結果を返すという問題があります。両方のモードが /fp:precise を使用していることを確認したので、問題にはなりません。これに関する主な問題は、完全な画像分析 (画像理解プロジェクト) が完全に決定論的であり、ランダムなものがまったくないことです。

これに関するもう 1 つの問題は、私のリリース ビルドが実際には常に同じ結果 (イメージの場合は 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 を非アクティブにするとリリースと同じ結果が得られます。

4

5 に答える 5

12

少なくとも 2 つの可能性:

  1. 最適化をオンにすると、コンパイラが操作を並べ替える可能性があります。これにより、操作の並べ替えが発生しないデバッグ モードで実行される順序と比較すると、浮動小数点計算にわずかな違いが生じる可能性があります。これにより、デバッグとリリースの間の数値の違い説明される場合がありますが、デバッグ モードでの実行ごとの数値の違いは説明されません。
  2. 配列の境界を越えた読み取り/書き込み、初期化されていない変数の使用、割り当てられていないポインターの使用など、コードにメモリ関連のバグがあります。優れた Valgrind などのメモリ チェッカーで実行してみてください。そのような問題を特定します。メモリ関連のエラー、非決定的な動作の原因となる場合があります。

Windows を使用している場合、Valgrindは利用できませんが (残念)、代わりのリストについてはこちらを参照してください。

于 2012-08-14T14:18:24.270 に答える
6

私のコメントを詳しく説明すると、これはおそらくあなたの問題の根源であるコードです:

#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)
    {

lastlastprivate再度読み取られる前にのみ割り当てられるため、並列領域外の最後の反復からの値が本当に必要な場合は、変数にするのに適しています。それ以外の場合は、それを作成して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)

注意すべきもう 1 つの点は、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基づいています。現在、重要な領域は 1 つだけで、コードの最後にあります。これも回避できますが、追加の配列を導入する必要があります。

于 2012-08-15T13:00:18.787 に答える
4

再確認すべきことの 1 つは、すべての変数が初期化されていることです。多くの場合、最適化されていないコード (デバッグ モード) はメモリを初期化します。

于 2012-08-14T14:20:47.180 に答える
2

初期化されていない変数、不正なポインタ、シーケンスポイントが介在しない同じオブジェクトの複数の変更など、ほぼすべての未定義の動作がこれを説明できます。結果が再現できない場合があるという事実は、初期化されていない変数についていくらか主張しますが、ポインタの問題や境界エラーからも発生する可能性があります。

特にIntelでは、最適化によって結果が変わる可能性があることに注意してください。最適化により、どの中間値がメモリに流出するかが変わる可能性があります。括弧を注意深く使用しなかった場合は、式の評価の順序も変更できます。(そして、ご存知のとおり、マシンの浮動小数点では(a + b) + c) != a + (b + c)、結果は決定論的である必要があります。最適化の程度に応じて異なる結果が得られますが、最適化フラグのセットについては、同じ結果が得られるはずです。

于 2012-08-14T14:33:35.643 に答える
2

デバッグでの変数の初期化とリリースでの変数の初期化はありませんでした。しかし、あなたの結果はこれを裏付けません(リリースの信頼できる結果)。

コードは特定のオフセットまたはサイズに依存していますか? デバッグ ビルドは、いくつかの割り当ての周りにガード バイトを配置します。

浮動小数点関連でしょうか?

デバッグ浮動小数点スタックは、より効率的に構築されたリリースとは異なります。

ここを見てください:http://thetweaker.wordpress.com/2009/08/28/debugrelease-numerical-differences/

于 2012-08-14T14:26:35.187 に答える