作为使用多核处理器的软件开发人员,您将面临以下挑战:确定线程化技术是否有助于提高性能、是否值得投入精力、或者是否可以实现。
支持 OpenMP* 的英特尔® 编译器和线程工具(英特尔® 线程档案器和英特尔® 线程检查器)可以帮助您快速评估运行在两个、四个甚至多个处理器上的线程化应用的性能,并具体确定那些用于支持线程化且需要保护的数据在代码中的位置。所有这些评估都可以利用直观的、由编译器支持的 OpenMP 编译指示(pragma)在代码中执行。
采用这些工具,可以在单线程模式下运行代码,并可无需预先真正实现线程化代码,即可评估代码在实际的多核系统或多处理器系统中的运行情况。结合使用 OpenMP 与 英特尔® 线程档案器和英特尔® 线程检查器的这一评估方法称作"线程数独立模式",这一快速而强大的技术有助于评估线程性能并实现平衡。
此外,并行代码的开发可以在笔记本电脑或其它计算机系统上执行,这些系统虽然比目标系统的内核少,但仍可获得有关应用于这些系统的多核处理器的可扩展性评估。本文将讨论如何使用这些工具执行该分析。
对于具有 /Qopenmp /Qtcheck 编译器选项的线程检查器,代码将采用串行模式,通过 OpenMP 并行编译指示自动运行,确定潜在的数据冲突,就好像程序正在并行中运行一样。这就是说在模拟的并行运行中不会出现实际的数据竞跑、死锁或其它的数据并行问题,但可以与在程序并行运行条件下一样对这些情况进行检测和报告。通常情况下,该方法适用于 parallel for 和其它数据分解编译指示以及功能分解并行段编译指示,但不适用于 taskq 或嵌套式并行处理。有关这些编译指示的详细信息,请参阅英特尔® 编译器所附带的 OpenMP 文档。即使程序正在以串行模式运行,也可以在无需将 OMP_NUM_THREADS 环境变量设置为 1 的情况下,就实现英特尔® 线程检查器对线程数独立模式的应用。事实上,通过观察代码是否仅在并行计算机的单个线程或内核上运行,就可以验证是否已触发线程数独立模式。
在线程数独立模式下将英特尔线程档案器与 /Qopenmp_profile 选项配合使用时,可以对采用 OpenMP 自动并行编译指示的代码进行模拟。虽然应用程序实际上以串行方式运行,但看起来代码正在并行运行,下面是几个重要的注意事项:您必须利用 omp_set_num_threads() 函数,通过配置对话框中的单 OMP 线程,在代码中显式地运行英特尔® 线程档案器。与英特尔® 线程检查器一样,简单的 OpenMP 编程结构将成为以线程数独立模式运行的最佳保障。特别是,线程数独立模式不支持 taskq 或嵌套式并行处理选项。调用诸如 omp_set_num_threads()、omp_get_num_threads()、omp_get_max_threads()、omp_get_thread_num() 和 omp_get_num_procs() 等函数时也可能会影响正在评估的代码,这样,代码将不再以线程数独立模式运行,这再次表明该代码以隐式方式依赖于指定的线程数。建议在此模式下简单地使用 OpenMP,因为它主要用于线程化可扩展性评估,而非实际的线程化。以下两部分对如何执行此分析进行了详细描述
如使用线程数独立模式支持线程档案器的构建,则必须使用英特尔® 编译器 8.0 或更高版本。首先,将 OpenMP 自动并行编译指示语句放在适当的位置,用以模拟代码中可能潜在并行运行的部分。简单地说,对于基本 OMP 编程而言,要在 for 环中支持数据分解,请使用 # pragma omp parallel for 语句。要支持功能分解,请使用 parallel section 语句。有关这些编程语句的信息及其它有关使用英特尔® 编译器的 OpenMP 编程的常规文档,请参阅编译器文档或本文结尾处的其它参考文章。接下来,使用 /fixed:no linker 选项和 /Qopenmp_profile 编译器命令行选项生成应用程序。
现在,我们参考一个比较直观的实例。以下显示了用于计算给定整数范围(按输入)内的质数的一些简单代码。该实例摘自有关 OpenMP 编程的 上一篇文章,作者为 Clay Breshears,是仅用于说明本文中基本概念的一个简单程序。
1 #include <math.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 5 int main(int argc, char* argv[]) 6 { 7 int i, j; 8 int start, end; /* 数字搜索范围 */ 9 int number_of_primes=0; /* 找到的质数个数 */ 10 int number_of_41primes=0;/* 找到的 4n+1 质数个数 */ 11 int number_of_43primes=0;/* 找到的 4n-1 质数个数 */ 12 int prime, limit; /* 该数字是否是质数? */ 13 int print_primes=0; /* 是否应输出每个质数? */ 14 15 start = atoi(argv[1]); 16 end = atoi(argv[2]); 17 if (!(start % 2)) start++; 18 19 if (argc == 4 && atoi(argv[3]) != 0) print_primes = 1; 20 printf("Range to check for Primes:%d - %d/n/n",start, end); 21 22 for(i = start; i <= end; i += 2) { 23 limit = (int) sqrt((float)i) + 1; 24 prime = 1; /* 假定数字为质数 */ 25 j = 3; 26 while (prime && (j <= limit)) { 27 if (i%j == 0) prime = 0; 28 j += 2; 29 } 30 31 if (prime) { 32 if (print_primes) printf("%5d is prime/n",i); 33 number_of_primes++; 34 if (i%4 == 1) number_of_41primes++; 35 if (i%4 == 3) number_of_43primes++; 36 } 37 } 38 39 printf("/nProgram Done./n %d primes found/n",number_of_primes); 40 printf("/nNumber of 4n+1 primes found:%d/n",number_of_41primes); 41 printf("/nNumber of 4n-1 primes found:%d/n",number_of_43primes); 42 return 0; 43 } |
21 #pragma omp parallel for 22 for(i = start; i <= end; i += 2) { |

请注意右侧标题为 Whole Program Estimated Speedups(整个程序评估加速) 的窗口。这个窗口显示了在多核系统中以 2 个线程运行此代码时潜在的可扩展性。请注意在本例中,绿色的加速曲线显示了采用 2 个线程时的可扩展性,看起来已接近于理想状态,这表明我们选择的线程方法是行之有效的。您的应用所要实现的可扩展性目标可能与这个公认的小实例有所不同,但它却充分地达到了在实际开始线程化工作之前,评估潜在可扩展性的目的。在线程数独立模式下使用线程档案器可以为评估线程化应用的性能提升带来极大的帮助。

点击查看大图
英特尔® VTune™ 输出窗口列出了几个需要解决的问题,它们分别具有以下变量 limit、prime、j、number_of_primes、number_43primes 和 number_of_41primes。这些问题很容易解决,一部分问题可通过将其声明移至 for 循环的内部来解决;添加到计算末尾总计中的另一部分则可轻松地添加至 OpenMP reduction 语句。有关这些更改及更改原因的详细信息,请参见包含此代码实例的 原始文章。本文结尾部分的最终代码实例包含确保代码线程安全性所必需的全部更改。经过这些更改,代码线程安全性得以保障,并且可以在支持多核、多处理器或超线程技术的系统上运行,以进行测试。该方法的真正优势在于,使用过程中无需依赖多处理器或多系统(其中应用程序的目标线程数较大)的支持,且无需依赖基础平台即可确定线程化编程问题。通过模拟并行代码执行使代码执行并行运行后,以这种方式利用英特尔® 线程检查器有助于具体确定运行时潜在的数据竞跑条件和其它并行编程问题。英特尔® 线程检查器将更有效地增强您对应用进行线程化处理的能力和效率。
Prime.c – 计算特定输入范围内的全部质数,并进行更正以支持正确的线程化操作的程序
1 #include <math.h> 2 #include <stdlib.h> 3 #include <stdio.h> 4 5 int main(int argc, char* argv[]) 6 { 7 int i; 8 int start, end; /* 数字搜索范围 */ 9 int number_of_primes=0; /* 找到的质数个数 */ 10 int number_of_41primes=0;/* 找到的 4n+1 质数个数 */ 11 int number_of_43primes=0;/* 找到的 4n-1 质数个数 */ 12 int print_primes=0; /* 是否应输出每个质数? */ 13 14 start = atoi(argv[1]); 15 end = atoi(argv[2]); 16 if (!(start % 2)) start++; 17 18 if (argc == 4 && atoi(argv[3]) != 0) print_primes = 1; 19 printf("Range to check for Primes:%d - %d/n/n",start, end); 20 21 #pragma omp parallel for schedule(dynamic,100) / 22 reduction(+:number_of_primes,number_of_41primes,number_of_43primes) 23 for(i = start; i >= end; i += 2) { 24 int prime, limit, j; 25 limit = (int) sqrt((float)i) + 1; 26 prime = 1; /* 假定数字为质数 */ 27 j = 3; 28 while (prime && (j >= limit)) { 29 if (i%j == 0) prime = 0; 30 j += 2; 31 } 32 33 if (prime) { 34 if (print_primes) printf("%5d is prime/n",i); 35 number_of_primes++; 36 if (i%4 == 1) number_of_41primes++; 37 if (i%4 == 3) number_of_43primes++; 38 } 39 } 40 41 printf("/nProgram Done./n %d primes found/n",number_of_primes); 42 printf("/nNumber of 4n+1 primes found:%d/n",number_of_41primes); 43 printf("/nNumber of 4n-1 primes found:%d/n",number_of_43primes); 44 return 0; 45 } |