同步影响

来源:互联网 发布:java 调用log4j 编辑:程序博客网 时间:2024/04/28 18:24

 尽管在新的测试代码中等待时间已经很小,但是线程还需花费一部分时间用于等待同步。随着负载不均衡,我们能够从实例代码清楚地了解到,在何处线程争用同步。对于更复杂的情况,可使用英特尔® 线程档案器的 Regions View 来判断哪些并行域包含这种冲突,并使您将精力主要集中于这些地方。

由同步保护的代码段应尽可能地简短,并保持正确的代码。采用这条原则,可以将代码花费在等待访问受保护代码段的时间降至最低。对于示例代码,关键段应尽可能得小。没有任何外来声明(extraneous statement)不需要线程独占访问。每个声明都应放置在独立的关键代码段中。在这种情况下,我们应该对关键代码段进行命名,因为在 OpenMP 中,所有未命名的关键代码段都视作同一代码段,即便它们以完全不同的函数与源文件出现也是如此。为了同实例代码使用关键代码段,将计数器的增量改为:

#pragma omp critical (cs1)
        number_of_primes++;
#pragma omp critical (cs2)
        if (i%4 == 1) number_of_41primes++;
#pragma omp critical (cs3)
        if (i%4 == 3) number_of_43primes++;

 

采用四个线程与三个关键代码段,则很有可能至少有一个线程将等待进入关键代码段。此外,进入与退出多个关键代码段的开销将变为三倍。英特尔® 线程档案器得出的结果表明,当采用三个命名关键代码段覆盖原始的三线(three-line)关键代码段时,线程等待锁定的时间百分比与并行开销扩大了两倍第二条需要遵循的经验为,不要在循环中放置同步。针对实例代码,此处有两种选择来实施该建议:第一种方法是每个计数器均采用临时变量,这些临时变量声明用于在每个线程中创建专有拷贝。这些局部变量会在循环中增加,退出循环前,当任务分割构成后,在一个单独的关键代码段中将局部变量值添加入全局变量值中。因此,代码中仅有一处可能会发生某线程延迟其他线程的问题。该方法的一个缺陷即,需要对串行代码进行更改来满足并行要求。


 #pragma omp parallel
{ int numPrimes=0;    /* local number of primes found */
  int num41Primes=0;  /* local number of 4n+1 primes found */
  int num43Primes=0;  /* local number of 4n-1 primes found */

#pragma omp for schedule(dynamic,100)
  for(i = start; i <= end; i += 2) {
    int limit, j, prime;

    limit = (int) sqrt((float)i) + 1;
    prime = 1; /* assume number is prime */
    j = 3;
    while (prime && (j <= limit)) {
       if (i%j == 0) prime = 0;
       j += 2;
    }

    if (prime) {
 if (print_primes) printf("%5d is prime/n",i);
      numPrimes++;
      if (i%4 == 1) num41Primes++;
      if (i%4 == 3) num43Primes++;
    }
  } // end for

#pragma omp critical
  { // Update global counter values with local values
    number_of_primes += numPrimes;
    number_of_41primes += num41Primes;
    number_of_43primes += num43Primes;
  }
} // end parallel region

 

第二种方法同样采用 OpenMP 功能,来执行与上述相同的操作,不同的地方是该方法不对串行代码进行任何更改。OpenMP 归约语句创建共享变量的专用拷贝,利用每个线程中的这些专用拷贝进行计算,并将所有的部分结果合并返回至并行区域末端的原始变量。归约语句的语法要求在并行区域完成时,将关联二元运算符与变量名称同操作符相结合。在对实例代码完成此项更改后(详情参见下列代码),英特尔® 线程档案器所获得的结果可实现几近完美(99.984%)的并行执行。

原创粉丝点击