我正在尝试并行处理我的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中发生的递增来实现它。
如果您需要更多信息或代码,请告诉我。谢谢。
答案 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
,则是告诉编译器无关紧要。
以下是解决此问题的一些方法:
使用GMP精确地进行添加。有一个库GMP,可以将双精度数转换为有理数。然后,您可以将有理数加在一起,无论执行什么顺序,结果都是一样的,因为没有舍入。参见https://gmplib.org/manual/Rational-Number-Functions.html#Rational-Number-Functions
添加后,您可以将有理数转换为双精度数,虽然这并不精确,但是每次都以相同的方式不精确。
使用定点精确进行加法。这样的好处是您不需要使用外部库。
将数字转换为固定点,如下所示:
#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;
}
此方法并不精确,但是无论加法顺序如何,每次都完全不一样。
将每个数字加载到内存中,然后进行加法。创建一个二维数组。每一列代表一个文件。每行是每个文件中的一行。用伪代码:
#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
个字节