单/多线程(OpenMP)模式下计算精度的差异

时间:2014-12-28 17:52:49

标签: multithreading visual-c++ parallel-processing openmp precision

有人能解释/理解单/多线程模式下的计算结果的不同吗?

以下是约的例子。计算pi:

#include <iomanip>
#include <cmath>
#include <ppl.h>

const int itera(1000000000);

int main()
{
    printf("PI calculation \nconst int itera = 1000000000\n\n");

    clock_t start, stop;

    //Single thread
    start = clock();
    double summ_single(0);
    for (int n = 1; n < itera; n++)
    {
        summ_single += 6.0 / (static_cast<double>(n)* static_cast<double>(n));
    };
    stop = clock();
    printf("Time single thread             %f\n", (double)(stop - start) / 1000.0);


    //Multithread with OMP
    //Activate OMP in Project settings, C++, Language
    start = clock();
    double summ_omp(0);
#pragma omp parallel for reduction(+:summ_omp)
    for (int n = 1; n < itera; n++)
    {
        summ_omp += 6.0 / (static_cast<double>(n)* static_cast<double>(n));
    };
    stop = clock();
    printf("Time OMP parallel              %f\n", (double)(stop - start) / 1000.0);


    //Multithread with Concurrency::parallel_for
    start = clock();
    Concurrency::combinable<double> piParts;
    Concurrency::parallel_for(1, itera, [&piParts](int n)
    {
        piParts.local() += 6.0 / (static_cast<double>(n)* static_cast<double>(n)); 
    }); 

    double summ_Conparall(0);
    piParts.combine_each([&summ_Conparall](double locali)
    {
        summ_Conparall += locali;
    });
    stop = clock();
    printf("Time Concurrency::parallel_for %f\n", (double)(stop - start) / 1000.0);

    printf("\n");
    printf("pi single = %15.12f\n", std::sqrt(summ_single));
    printf("pi omp    = %15.12f\n", std::sqrt(summ_omp));
    printf("pi comb   = %15.12f\n", std::sqrt(summ_Conparall));
    printf("\n");

    system("PAUSE");

}

结果:

PI calculation VS2010 Win32
Time single thread 5.330000
Time OMP parallel 1.029000
Time Concurrency:arallel_for 11.103000

pi single = 3.141592643651
pi omp = 3.141592648425
pi comb = 3.141592651497


PI calculation VS2013 Win32
Time single thread 5.200000
Time OMP parallel 1.291000
Time Concurrency:arallel_for 7.413000

pi single = 3.141592643651
pi omp = 3.141592648425
pi comb = 3.141592647841


PI calculation VS2010 x64
Time single thread 5.190000
Time OMP parallel 1.036000
Time Concurrency::parallel_for 7.120000

pi single = 3.141592643651
pi omp = 3.141592648425
pi comb = 3.141592649319


PI calculation VS2013 x64
Time single thread 5.230000
Time OMP parallel 1.029000
Time Concurrency::parallel_for 5.326000

pi single = 3.141592643651
pi omp = 3.141592648425
pi comb = 3.141592648489

测试是在AMD和Intel CPU上进行的,Win 7 x64。

单核和多核PI计算差异的原因是什么? 为什么Concurrency :: parallel_for的计算结果在不同的版本(编译器,32/64位平台)上不是常量?

P.S。 Visual Studio express不支持OpenMP。

3 个答案:

答案 0 :(得分:6)

由于舍入误差,浮点加法是一种非关联运算,因此操作顺序很重要。让并行程序给出与串行版本不同的结果是正常的。理解和处理它是书写(便携式)并行代码的一部分。由于在32位模式下VS编译器使用x87指令而x87 FPU执行内部精度为80位的所有操作,因此在32位对64位版本中会加剧这种情况。在64位模式下,使用SSE数学。

在串行情况下,一个线程计算s 1 + s 2 + ... + s N ,其中 N 是扩展中的术语数。

在OpenMP案例中,有 n 部分和,其中 n 是OpenMP线程的数量。哪些项进入每个部分和取决于迭代在线程之间分配的方式。许多OpenMP实现的默认值是静态调度,这意味着线程0(主线程)计算ps 0 = s 1 + s 2 + ... + s N / n ;线程1计算ps 1 = s N / n + 1 + s N / n + 2 + ... + s 2N / N ;等等。最后,减少以某种方式结合了这些部分总和。

parallel_for案例与OpenMP案例非常相似。不同之处在于,默认情况下,迭代以动态方式分布 - 请参阅auto_partitioner的文档,因此每个部分和包含或多或少的随机选项。这不仅给出了稍微不同的结果,而且每次执行时结果也略有不同,即具有相同线程数的两个连续parallel_for的结果可能略有不同。如果使用simple_partitioner实例替换分区程序并将块大小设置为itera / number-of-threads,则应该获得与OpenMP案例中相同的结果,如果执行缩减同样的方式。

你可以使用Kahan summation并使用Kahan求和来实现你自己的缩减。然后,并行代码应该产生与序列代码相同(更加相似)的结果。

答案 1 :(得分:5)

我猜想openmp所做的并行缩减通常更准确 浮点加法舍入误差得到更多分布。一般浮点 由于舍入错误等原因,减少是有问题的。http://floating-point-gui.de/ 并行执行这些操作是通过分配舍入误差来提高准确性的一种方法。想象一下,你正在大幅度减少,在某些时候累加器的尺寸会比其他值大,这会增加每次加法的舍入误差,因为累加器范围要大得多,并且可能无法表示该范围内较小值的值是准确的,但是如果有多个累加器用于同一减少并行操作,它们的幅度将保持较小,这种误差会更小。

答案 2 :(得分:-2)

所以... 在win32模式下,将使用具有80位寄存器的FPU。 在x64模式下,将使用具有双精度浮点(64位)的SSE2。在x64模式下,默认使用sse2似乎是默认的。

理论上...... win32模式下的计算可能会更准确吗? :) http://en.wikipedia.org/wiki/SSE2 那么更好的方法是用AVX购买新的CPU或编译成32位代码?...