OpenMP C-并行循环平均给出不同的结果

时间:2019-01-12 19:50:04

标签: c parallel-processing openmp average

我正在尝试并行处理我的C程序,该程序读取具有数据的5000个文件并平均文件中的每一行。 例如

文件1:

1
2

文件2:

3
4

输出为:

2
3

程序运行正常,直到将其并行化,然后每次运行都得到不同的答案。 该程序如下所示:

#pragma omp parallel for        
for(int i=0; i<numFiles; i++){
    //Process FILES
    processFile(argv[i+2],alpha,entropySum,entropy2Sum);
}

processFiles在argv中打开文件,并将第i行中的数据添加到数组entropySum [i],将data ^ 2添加到数组entropy2Sum [i]。 添加所有数据后,我使用

输出信息
for(int i=0; i<=TF; i++){
    double avg=0, avg2=0, stdDev=0;
    avg = entropySum[i]/numFiles;
    avg2 = entropy2Sum[i]/numFiles;
    stdDev = sqrt(avg2-pow(avg,2));
    printf("%d\t%.12e\t%.12e\n",i,avg,stdDev);
}

这是我得到的不同结果的一个示例:

$ ./calcEntropy 1 data/2019_01_07/L06/p_00/data*
0   0.000000000000e+00  0.000000000000e+00
1   3.805323999338e-01  1.739913615303e-01
2   9.195334281358e-01  1.935111917992e-01
3   1.263129144934e+00  1.592392740809e-01
4   1.437299446640e+00  1.069415304025e-01
**5 1.519477007427e+00  7.899186640180e-02**
6   1.540955646645e+00  8.134009585552e-02
7   1.562133860024e+00  6.672275284387e-02
**8 1.573666031035e+00  7.200051995992e-02**
9   1.577305749778e+00  6.905825416624e-02
10  1.584251429260e+00  7.170811783630e-02
11  1.606099624837e+00  7.026618801497e-02
12  1.587069341648e+00  6.714269884875e-02

$ ./calcEntropy 1 data/2019_01_07/L06/p_00/data*
0   0.000000000000e+00  0.000000000000e+00
1   3.805323999338e-01  1.739913615303e-01
2   9.195334281358e-01  1.935111917992e-01
3   1.263129144934e+00  1.592392740809e-01
4   1.437299446640e+00  1.069415304025e-01
**5 1.519114903273e+00  8.255618715434e-02**
6   1.540955646645e+00  8.134009585553e-02
7   1.562133860024e+00  6.672275284389e-02
**8 1.573666031035e+00  6.715192373223e-02**
9   1.577305749778e+00  6.905825416623e-02
10  1.584251429260e+00  7.170811783631e-02
11  1.606099624837e+00  7.026618801500e-02
12  1.587069341648e+00  6.714269884876e-02

在网上检查了一切之后,我觉得我应该使用减少量吗?但是我不确定如何在此循环中使用两个要递增的变量(entropySum和entropy2Sum)以及在函数processFile中发生的递增来实现它。

如果您需要更多信息或代码,请告诉我。谢谢。

1 个答案:

答案 0 :(得分:1)

我猜测问题在于浮点数学运算没有关联性。参见https://en.wikipedia.org/wiki/Associative_property#Nonassociativity_of_floating_point_calculation

换句话说,如果您有浮点数a,b和c,则(a + b) + c并不总是等于a + (b + c)。您执行添加的顺序很重要。但是,如果您使用#pragma omp parallel for,则是告诉编译器无关紧要。

以下是解决此问题的一些方法:

  1. 使用GMP精确地进行添加。有一个库GMP,可以将双精度数转换为有理数。然后,您可以将有理数加在一起,无论执行什么顺序,结果都是一样的,因为没有舍入。参见https://gmplib.org/manual/Rational-Number-Functions.html#Rational-Number-Functions

    添加后,您可以将有理数转换为双精度数,虽然这并不精确,但是每次都以相同的方式不精确。

  2. 使用定点精确进行加法。这样的好处是您不需要使用外部库。

    将数字转换为固定点,如下所示:

    #define SCALE_FACTOR 100
    // Convert from double to fixed point
    // Example: 15.46   becomes 1546
    // Example: 3.14159 becomes 314
    int convert_d_to_fixed(double d) {
        return (int) d * SCALE_FACTOR
    }
    

    转换为固定点不精确。但是,一旦完成转换,由于使用整数运算,定点中的所有加法和减法都是精确的。

    int add_fixed(int a, int b) {
        return a + b;
    }
    

    添加完成后,您可以转换回去:

    double convert_fixed_to_d(int f) {
        return ((double) f) / SCALE_FACTOR;
    }
    

    此方法并不精确,但是无论加法顺序如何,每次都完全不一样。

  3. 将每个数字加载到内存中,然后进行加法。创建一个二维数组。每一列代表一个文件。每行是每个文件中的一行。用伪代码:

    #pragma omp parallel for        
    for(int i=0; i<numFiles; i++){
        load file i into column i
    }
    #pragma omp parallel for        
    for(int j=0; j<numRows; j++){
        sum row j into entropySum[j]
        sum square of elements in row j into entropy2Sum[j]
    }
    

    这总是以相同的顺序进行加法操作,但是在加载数据和添加数据时仍然允许并行操作。

    缺点:必须立即将整个数据集加载到内存中。这需要(number of files) * (number of rows) * 8个字节