Debug和Release之间的结果不同

时间:2012-08-14 14:14:18

标签: c++ visual-studio-2010 openmp release-mode debug-mode

我遇到的问题是,在将debug与r​​elease进行比较时,我的代码会返回不同的结果。我检查了两种模式都使用/ 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相同的结果。

5 个答案:

答案 0 :(得分:8)

至少有两种可能性:

  1. 启用优化可能会导致编译器重新排序操作。与调试模式中执行的顺序相比,这可能会在浮点计算中引入较小的差异,在调试模式下不会发生操作重新排序。这个可能说明了调试和发布之间的数值差异,但说明了在调试模式下从一次运行到下一次运行的数值差异。
  2. 您的代码中存在与内存相关的错误,例如读取/写入数组的边界,使用未初始化的变量,使用未分配的指针等。尝试通过内存检查器运行它,例如优秀的Valgrind,找出这样的问题。与内存相关的错误可能会导致非确定性行为。
  3. 如果您使用的是Windows,则Valgrind无效(可惜),但您可以查看here以获取备选列表。

答案 1 :(得分:4)

要仔细检查的一件事是所有变量都已初始化。很多时候未优化的代码(调试模式)会初始化内存。

答案 2 :(得分:4)

要详细说明我的评论,这段代码很可能是您问题的根源:

#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循环的计数器和并行区域内定义的变量之外,所有变量都是共享的,即除非您同时应用{shared子句,否则不执行任何操作。 {1}}条款。

您应该注意的另一件事是,在32位模式下,Visual Studio使用x87 FPU数学,而在64位模式下,它默认使用SSE数学。 x87 FPU使用80位浮点精度进行中间计算(即使仅涉及default(none)的计算),而SSE单元仅支持标准IEEE单精度和双精度。将OpenMP或任何其他并行化技术引入32位x87 FPU代码意味着在某些点处,中间值应该转换回float的单精度,并且如果进行了足够多次的轻微或显着差异(取决于在串行码和并行码的结果之间可以观察到算法的数值稳定性。

根据您的代码,我建议以下修改后的代码可以提供良好的并行性能,因为每次迭代都没有同步:

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; } } radcVal最好的价值观现在只有一个关键区域,它位于代码的末尾。你也可以绕过它,但你必须引入额外的数组。

答案 3 :(得分:2)

我会说调试中的变量初始化而不是发布中的变量初始化。但是你的结果不会支持这一点(发布时可靠的结果)。

您的代码是否依赖于任何特定的偏移量或大小?调试版本会在某些分配周围放置保护字节。

它可能与浮点相关吗​​?

调试浮点堆栈与为提高效率而构建的版本不同。

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

答案 4 :(得分:2)

几乎任何未定义的行为都可以解释这个问题:未初始化 变量,流氓指针,同一对象的多个修改 没有干预序列点等等。事实上 结果有时是无法解决的争论 未初始化的变量,但它也可能出现指针问题或 边界错误。

请注意,优化可能会改变结果,尤其是在英特尔上。 优化可以改变哪些中间值溢出到内存,和 如果你没有仔细使用括号,甚至是评价顺序 在表达中。 (众所周知,在机器浮动点(a + b) + c) != a + (b + c)。)结果仍然是确定性的: 你会根据优化程度得到不同的结果, 但是对于任何一组优化标志,你应该得到相同的结果。