OpenMP并行计算素数

时间:2017-12-12 03:13:46

标签: c multithreading parallel-processing openmp

以下是计算素数的简单代码:

#define END   100000000

// Only pass odd values to this function
int is_prime(uint32_t v)
{
    uint32_t end = sqrt(v);
    for (uint32_t i = 3; i <= end; i += 2) {
        if ((v % i) == 0) {
            return 0;
        }
    }
    return 1;
}

int main(int argc, char **argv)
{
    // We'll grab 2 as it's the only even prime
    int prime_count = 1;

    uint32_t bracket = 10;

    #pragma omp parallel for num_threads(4)
    for (uint32_t i = 3; i < END; i += 2) {
        if (i > bracket) {
            printf("%12d\t%12d\n", bracket, prime_count);
            bracket *= 10;
        }
        if (is_prime(i)) {
            prime_count++;
        }
    }
    printf("%12d\t%12d\n", bracket, prime_count);
    return 0;
}

我们的任务是使用OpenMP pragma来加速计算,因为循环100,000,000个整数需要很长时间。我尝试将#pragma语句放在我粘贴的代码中的当前位置,当我运行代码时,我得到以下输出:

        10              4
        10              1
        10              4
        10              4
       100             25
      1000             25
     10000             25
    100000             25
  10000000            262
1410065408        5737822

显然这看起来不正确,我尝试在is_prime()函数的for循环上面放一个#pragma,但是我遇到了编译错误

错误:“与OpenMP结构块无关的分支”。

我意识到这个错误来自可能进入if语句的线程,并在并行操作的中间返回0。线程过早终止,整个操作分开。我觉得将#pragma放在函数调用中是最合适的,因为它可以在每次调用函数时将运行时间减少v / 4。但是,我不知道这样做的正确语法。如果有人能帮助我,我会非常感激。感谢

3 个答案:

答案 0 :(得分:2)

不要寻求语法上的改变,有必要改变思维的概念 - 它是并行发生的

鉴于上面已经指示了num_threads( 4 ),即将进行的操作除了其他三个之外还会发生。这意味着如果一个线程进入prime_count++并获取prime_count的“当前”值,以便添加+1,作为指令命令,其他线程无法在您的代码中等待,在第一个接受者完成++之前并存储“新”值(应该由其他接受者使用)。在没有任何协调工具的情况下,不受控制的添加在第一眼看上去看起来很好,但是一旦你意识到,你有很多次错误的“初始”值(在很多情况下可以同时 {{1} } - 由任何其他线程组成),所以最终的结果只是一场残骸破坏。

可以明确协调和/或共享变量,但时间很长。

++

在生成正确的结果后,性能很重要:

更好的举措是主要避免在所有级别上发生冲突读取/写入(最好避免错误对齐和缓存效率低下):

希望你也可以享受扩展的语法修饰,借用true-[PARALLEL] language occam-pi,以强调线程对齐的代码执行)

[END] PRIME COUNT was omp.FOUND ==    664579 <= 10000000
[END] PRIME COUNT was omp.FOUND ==   1270607 <= 20000000
[END] PRIME COUNT was omp.FOUND ==   1857859 <= 30000000

INF(t:  0)|||: _id
|||(t:  0)|||:          10                1
|||(t:  0)|||:         100                7
|||(t:  0)|||:        1000               44
|||(t:  0)|||:       10000              311
INF(t:  3)|||: _id             
|||(t:  3)|||:          10                0
|||(t:  3)|||:         100                5
|||(t:  3)|||:        1000               37
|||(t:  3)|||:       10000              295
INF(t:  2)|||: _id             
|||(t:  2)|||:          10                1
|||(t:  2)|||:         100                6
|||(t:  2)|||:        1000               43
|||(t:  2)|||:       10000              308
INF(t:  1)|||: _id             
|||(t:  1)|||:          10                1
|||(t:  1)|||:         100                6
|||(t:  1)|||:        1000               43
|||(t:  1)|||:       10000              314
|||(t:  0)|||:      100000             2409
|||(t:  1)|||:      100000             2399
|||(t:  3)|||:      100000             2384
|||(t:  2)|||:      100000             2399
|||(t:  2)|||:     1000000            19669
|||(t:  3)|||:     1000000            19552
|||(t:  1)|||:     1000000            19623
|||(t:  0)|||:     1000000            19653
|||(t:  2)|||:    10000000           166237
|||(t:  0)|||:    10000000           166161
|||(t:  1)|||:    10000000           166204
|||(t:  3)|||:    10000000           165976
:--(t:  0):--:   100000000           464549
:--(t:  1):--:   100000000           464491
:--(t:  2):--:   100000000           464530
:--(t:  3):--:   100000000           464288
[END] PRIME COUNT was omp.FOUND ==  1857859 <= 30000000

Feel free to further experiment with the code Live / on-line.

答案 1 :(得分:1)

所有线程可以同时传递if块并修改bracket不一致

编译指示“#pragma omp critical”可以防止这种情况:

#define END   100000000

// Only pass odd values to this function
int is_prime(uint32_t v)
{
    uint32_t end = sqrt(v);
    for (uint32_t i = 3; i <= end; i += 2) {
        if ((v % i) == 0) {
            return 0;
        }
    }
    return 1;
}

int main(int argc, char **argv)
{
    // We'll grab 2 as it's the only even prime
    int prime_count = 1;

    uint32_t bracket = 10;

    #pragma omp parallel for num_threads(4)
    for (uint32_t i = 3; i < END; i += 2) {
        #pragma omp critical
        {
            if (i > bracket) {
                printf("%12d\t%12d\n", bracket, prime_count);
                bracket *= 10;
            }
        }
        if (is_prime(i)) {
            prime_count++;
        }
    }
    printf("%12d\t%12d\n", bracket, prime_count);
    return 0;
}

上面的代码并不能完全回答你的问题:我想你想知道素数在10,100,1000之下的数量......

但处理for的openmp方法如下:它在4中切断循环:

  • the first from 0 to N/4,
  • the second from N/4 to N/2,
  • the first from N/2 to 3*N/4,
  • the second from 3*N/4 to N.

为了知道素数如何分布,你应该有一些不同的方法:

int main(int argc, char **argv)
{

    /* number_of_prime[0] is the number of primes between   0 and 10 
       number_of_prime[1] is the number of primes between 10 and 100 
       ...  */
    int number_of_prime[8] = {1, 0};

    uint32_t bracket = 10;

    #pragma omp parallel for num_threads(4)
    for (uint32_t i = 3; i < END; i += 2) {            
        if (is_prime(i)) {
            /* here, i is a prime */
            if (i > 10000000)
            {
                #pragma omp critical
                number_of_prime[7]++;
            }
            else if (i > 1000000)
            {
                #pragma omp critical
                number_of_prime[6]++;
            }
            else if (i > 100000)
            {
                #pragma omp critical
                number_of_prime[5]++;
            }
            else if (i > 10000)
            {
                #pragma omp critical
                number_of_prime[4]++;
            }
            else if (i > 1000)
            {
                #pragma omp critical
                number_of_prime[3]++;
            }
            else if (i > 100)
            {
                #pragma omp critical
                number_of_prime[2]++;
            }
            else if (i > 10)
            {
                #pragma omp critical
                number_of_prime[1]++;
            }
            else 
            {
                #pragma omp critical
                number_of_prime[0]++;
            }                            
        }            
    }

    /* add some printf hero to display results */

    return 0;
}

编辑:

正如祖兰所说,我至少应该使用atomic instead of critical

答案 2 :(得分:1)

user3666197为您提供了如何手动执行此操作的示例。这对理解很好,但你不需要自己做 - 而是你应该使用OpenMP提供的抽象 - 这是一种减少。减少将为每个线程提供一个私有变量,并且在并行计算之后,它们将一起添加到原始变量中。没有中间输出的正确示例只是:

int prime_count = 1;

#pragma omp parallel for reduction(+:prime_count)
for (uint32_t i = 3; i < END; i += 2) {
    if (is_prime(i)) {
        prime_count++;
    }
}
printf("%12d\t%12d\n", 0, prime_count);

并行重现中间输出。我宁愿所有线程都在括号上工作,然后报告聚合结果。通过减少仍然可以轻松实现这一点,并为您提供每个块中有多少素数的正确汇总信息:

int prime_count = 1;

#pragma omp parallel
for (uint32_t bracket = 10; bracket <= END; bracket *= 10)
{
    uint32_t start = bracket / 10;
    if (start == 1) start = 3;
    else start++;
    #pragma omp for reduction(+:prime_count)
    for (uint32_t i = start; i < bracket; i += 2) {
        if (is_prime(i)) {
            prime_count++;
        }
    }
    #pragma omp single
    printf("%12d\t%12d %p\n", bracket, prime_count);
}
printf("%12d\t%12d\n", 0, prime_count);

我将forparallel分开,因为一次又一次地重新打开并行区域可能代价高昂。