How to synchronize C threads with OpenMP

0

I have this code that tries to make a Geometric Series using OpenMP, but in the end it does not get the same result as when it is executed sequentially.

#include <omp.h>
#include <stdio.h>

#define LIM 1000000000

int main(void)
{
    float serie;
    long int i;
    #pragma omp parallel num_threads(2)
    {
        #pragma omp for 
        for(i = 1;i <= LIM;i++)
        serie += (1 / (float)i);
        #pragma omp barrier
    }
    printf("La Serie es: %.5f\n",serie);
    return 0;
}

The correct result: 15.40368

When working with OpenMP, it gives sporadic values in each execution.

    
asked by TheJammer 04.05.2017 в 06:10
source

1 answer

1
#define LIM 1000000000

double calculate(long value)
{
  double toReturn = 0.;

  for( ; value ; value-- )
    toReturn += 1.0 / (double)value;

  return toReturn;
}

int main()
{
  printf("%.5f\n",calculate(LIM));
}

The execution of this program yields a result of

21.30048

That differs a lot from the result you indicate, 15.40368 . The reason? float has only 6 representative digits, the rest can be considered junk. One can think that 6 digits give a lot of precision ... but in your case, that is, in 1e9 iterations the error is significant. In other words, when i>1e7 you are already accumulating an error of approximately 10 and in% co_of% of 100.

While working with i>1e7 for this operation, you will never get a correct result.

On the other hand, your code has a race condition:

    #pragma omp for 
    for(i = 1;i <= LIM;i++)
      serie += (1 / (float)i); // <<--- AQUI!!!

If we exploit the code a bit we get something similar to this:

    #pragma omp for 
    for(i = 1;i <= LIM;i++)
    {
      double temp = serie;   // (1)
      temp = temp + 1.0 / i;
      serie = temp;          // (2)
    }

You are assuming that float will deduct that openmp is a variable common to all iterations and that you should take care that dirty readings do not occur that alter the final result and this is not the case. What happens is that one of the two threads can end up overwriting the value of the previous thread: the two threads pass through (1) at the same time and then through (2), what happens is that only the second value prevails.

To avoid this inconsistency you have to modify the definition of the loop:

#pragma omp parallel num_threads(2)
{
    #pragma omp for reduction(+:serie)
    for(i = 1;i <= LIM;i++)
      serie += (1.0 / i);
}

What the serie modifier does is that it assigns to each execution thread a copy of reduction , in such a way that each thread will modify its own version of the variable. When both threads are finished, the two versions of the variable are added together and the result is stored in the variable serie that you have defined.

#pragma omp for reduction(+:serie)
//                        ^  ^^^
//                        |  variable a dividir
//                        operacion a ejecutar para fusionar los valores

As a final note, serie is not necessary in this case since the definition of the parallel loop includes the corresponding synchronization of the threads.

In short, your code should look like this:

#define LIM 1000000000

int main()
{
  double serie = 0; // No olvides inicializar las variables
  long i;
  #pragma omp parallel num_threads(2)
  {
    #pragma omp for reduction(+:serie)
    for(i = 1;i <= LIM;i++)
      serie += (1.0 / i);
  }
  printf("La Serie es: %.5f\n",serie);
  return 0;
}

NOTE : If you still have any doubts about the final result you can check the correct value in this link

    
answered by 04.05.2017 / 11:33
source