有人能解释/理解单/多线程模式下的计算结果的不同吗?
以下是约的例子。计算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。
答案 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位代码?...