摘要
开发人员可以选择使用多种方法在代码中引入并行处理能力。 本文概括介绍了使用英特尔® Parallel Composer 进行并行编码的几种方法,并对它们的主要优势进行了比较。 虽然英特尔 Parallel Composer 只适用于 Windows* 环境下的 C/C++ 开发,但许多方法同样适用于 Fortran 语言和 Linux* 环境(只要具备适当的编译器即可)。
本文是“英特尔多线程应用开发指南”系列的一部分,该系列介绍了针对英特尔® 平台开发高效多线程应用的指导原则。
背景
英特尔® 编译器可通过多种方法自动检测和优化适合并行执行的代码结构(例如矢量化和自动/并行化),其中大部分方法要求修改代码。 插入的编译指示或函数均依赖于真正执行并行线程分解和调度操作的运行时数据库,例如英特尔® CilkTM Plus、阵列构建模块(英特尔l® ArBB)、线程构建模块(英特尔® TBB)、OpenMP* 和 Win32* API。 这些方法之间的主要区别在于它们对执行详情的控制级别;一般来说,控制力度越大,所需代码变更的介入性便越强。
英特尔® CilkTM Plus
英特尔® C++ 编译器中包含的英特尔 Cilk Plus 语言扩展为 C 和 C++ 新增了精细化任务支持能力,使其能够轻松将并行性添加至新软件和现有软件,从而帮助高效利用多颗处理器。 英特尔 Cilk Plus 具备以下主要特性:
- 关键词集(_Cilk_spawn、_Cilk_sync 和 _Cilk_for),用于表达任务并行性。
- reducer,通过自动为各任务创建视图以消除任务间争用共享变量的现象,并在任务完成后将它们减至共享值。
- 数组符号,支持所有函数或运算的数据并行性,使其适用于所有或部分数组或标量。
- simd 编译指示,支持您表达矢量并行性,从而有效利用硬件 SIMD 并行性,并通过英特尔编译器编写符合标准的 C/C++ 代码。
- 初等函数,可同时在标量参数或阵列元素上调用。 您可以通过将"__declspec(vector)" (on Windows*) 和 "__attribute_((vector))" (on Linux*) 添加在函数签名之前,来定义初等函数。
cilk Keyword/pragma | 描述 | |
cilk spawn (关键词) | 修改函数调用声明,以告知运行时系统该函数可(但不要求)与调用者并行运行。 "cilk spawn"变体要求 #include <cilk/cilk.h> | |
cilk sync(关键词) | 表示当前函数必须等到产生的子返回后才可继续通过该点。 “cilk_sync”变体要求 #include <cilk/cilk.h> | |
cilk for(关键词) | 指定允许循环迭代并行运行的循环;可替代标准循环 C/C++。 该声明可将循环分成包含一次或多次循环迭代的数据块。 各个数据块可连续执行,并在循环执行期间以数据块的形式催生。 “cilk_for” 变体要求 #include <cilk/cilk.h> | |
cilk grainsize(编译指示) | 指定一个 cilk_for 循环的粒度(块)大小 | |
CILK_NWORKERS(环境变量) | 指定工作线程的数量 | |
例如:cilk spawn,cilk sync
在以下示例中,cilk_spawn 既不催生也不创建任何新的线程。 它向 Cilk Plus 运行时指明:自由工作线程可在调用 fib(n-1) 之后窃取代码,而且该行为与函数调用同时进行。
例如:cilk for、cilk Reducer
在以下示例中,cilk_for 可导致循环体中的多个代码实例催生成执行内核并以并行的方式执行。 该 reducer 可避免出现数据竞跑现象,并有助于在不锁定的情况下减少操作。
例如: 数组符号
在以下示例中,C/C++ 中的常见下标语法可由数组段描述符代替,以取得相同的结果。 使用数组符号和标准 C/C++ 循环的区别在于不隐含串行顺序。 因此,编译器的预期代码生成策略为矢量并行化,比如使用 SSE 指令以 SIMD 的方式实施加法。 该编译器生成矢量,适用于数组运算的 SIMD 代码,包括所有语言内置运算符,比如'+'、'*'、'&'、'&&'等。
例如:编译指示 simd
使用 #pragma simd 的矢量化指示编译器强制执行循环矢量化。 它旨在最大限度减少获取矢量化代码需进行的源代码更改。 该 simd 编译指示可用于对编译器即使使用"pragma vector always"或"pragma ivdep"等矢量化提示也无法正常自动矢量化的循环进行矢量化处理。
char foo(char *A, int n){ int i; char x = 0; #ifdef SIMD #pragma simd reduction(+:x) #endif #ifdef IVDEP #pragma ivdep #endif for (i=0; i<n; i++){ x = x + A[i]; } return x; }>icl /c /Qvec-report2 simd.cpp simd.cpp simd.cpp(12) (col. 3): remark: loop was not vectorized: existence of vector dependence. >icl /c /Qvec-report2 simd.cpp /DIVDEP simd.cpp simd.cpp(12) (col. 3): remark: loop was not vectorized: existence of vector dependence. >icl /c /Qvec-report2 simd.cpp /DSIMD simd.cpp simd.cpp(12) (col. 3): remark: SIMD LOOP WAS VECTORIZED.
例如: 初等函数
__declspec(vector) int vfun_add_one(int x) { return x+1; }>icl /c /Qvec-report2 elementalfunc.cpp elementalfunc.cpp elementalfunc.cpp(3) (col. 1): remark: FUNCTION WAS VECTORIZED.
如欲了解更多有关 Cilk Plus 和初等函数的详细信息,请参阅英特尔® C++ 编译器 XE 12.0 用户与参考指南,以及文章“初等函数:借助英特尔® Cilk Plus 用 C/C++ 编写数据并行代码”。
英特尔® 线程构建模块(英特尔 ® TBB)
(英特尔® TBB)是一个函数库,可以提供丰富的方法帮助在 C++ 程序中表达并行性,并充分发挥多核处理器性能的优势。 它呈现了更高级别的任务级并行性,抽象出相关平台细节和线程化机制以帮助提高性能和可扩展性,同时还可平稳地融入至面向对象的泛型 C++ 框架中。 英特尔 TBB 采用基于运行时的编程模型,为开发人员提供了基于类似 STL(标准模板库)的模板库的一般并行算法。
例如:
#include "tbb/task_scheduler_init.h" #include "tbb/blocked_range.h" #include "tbb/parallel_for.h" #include <vector> void foo() { tbb::task_scheduler_init init; size_t length = 1000000; std::vector<float> a(length, 2), b(length, 3), c(length, 0); tbb::parallel_for(tbb::blocked_range<size_t>(0, length), [&](const tbb::blocked_range<size_t> &r){ for (size_t i=r.begin(); i<r.end(); i++) c[i] = a[i] + b[i]; }, tbb::auto_partitioner()); }
英特尔 TBB 任务调度程序可自动执行负载平衡操作,如此开发人员便无需执行这项可能非常复杂的任务。 英特尔 TBB 任务调度程序将编程流程分解为诸多小任务,并在各线程之间平均分配。
英特尔 C++ 编译器和英特尔 TBB 均支持新的 C++11 lambda 函数,让 STL 和英特尔 TBB 算法的使用变得更加简单。 若想使用英特尔的 lambda 表达式实施,编程人员必须使用 /Qstd=3Dc++0X 编译器选项编译代码。
使用 OpenMP* 添加并行性
OpenMP 是可移植多线程应用开发的行业标准。 英特尔® C++ 编译器支持 OpenMP C/C++ 3.0 版 API 规范,有关该规范的具体内容请访问 OpenMP 网站 (http://www.openmp.org)。 使用 OpenMP 添加并行性的流程可由用户通过 OpenMP 指令来控制。 该方法对细粒度(循环级别)和粗粒度(函数级别)线程极为有效。 对于将串行应用程序转换成并行应用程序,OpenMP 指令是一种容易使用且作用强大的手段,它具有使应用程序因为在多核系统上并行执行而获得大幅性能提升的潜力。 这些指令需通过 /Qopenmp 编译器选项启用,如果没有编译器选项则将被忽略。 这一特点使用户能够通过相同的源代码构建串行和并行版本的应用。 对于共享内存的并行计算机,该特性还支持用户对串行和并行运行进行简单对比。
下表列出了常用的 OpenMP* 指令:
指令 | 描述 |
#pragma omp parallel for [clause] ... for - loop | 对紧随编译指示之后的循环进行并行化。 |
#pragma omp parallel sections [clause] ... { [#pragma omp section structured-block] ... } | 在并行组的线程之间分配不同代码段的执行任务。 每个结构化块由并行组中的一个线程在其隐式任务环境中执行一次。 |
#pragma omp master structured-block | 主结构中包含的代码由线程组中的主线程执行。 |
#pragma omp critical [ (name) ] structured-block | 提供针对结构化块的互斥访问。 在程序的任意位置,每次只允许执行一个关键代码段。 |
#pragma omp barrier | 用于对并行区域内多个线程的执行进行同步。 确保在任何线程开始执行越过屏障指令的任何代码之前,所有线程均已完成执行屏障前的各个代码。 |
#pragma omp atomic expression-statement | 使用硬件同步基元实现互斥。 关键代码段可提供到某代码块的互斥访问,而原子指令则提供到单个赋值语句的互斥访问。 |
#pragma omp threadprivate (list) | 指定要复制的全局变量列表,每个线程一个实例(即每个线程均持有一个独立的变量副本)。 |
例如:
void sp_1a(float a[], float b[], int n) { int i; #pragma omp parallel shared(a,b,n) private(i) { #pragma omp for for (i = 0; i < n; i++) a[i] = 1.0 / a[i]; #pragma omp single a[0] = a[0] * 10; #pragma omp for nowait for (i = 0; i < n; i++) b[i] = b[i] / a[i]; } } icl /c /Qopenmp par1.cpp par2.cpp(5): (col. 5) remark: OpenMP DEFINED LOOP WAS PARALLELIZED. par2.cpp(10): (col. 5) remark: OpenMP DEFINED LOOP WAS PARALLELIZED. par2.cpp(3): (col. 3) remark: OpenMP DEFINED REGION WAS PARALLELIZED.
/Qopenmp-report[n] 编译器选项可用来控制 OpenmMP 并行化工具的诊断消息级别,其中 n 是 0 和 2 之间的一个数值。 若想使用该选项,编程人员必须首先指定 /Qopenmp 选项。 如果没有为 n 指定具体值,选项将采用默认值 /Qopenmp-report1,此时的诊断消息将显示成功实现并行化的循环、区域和代码段。
鉴于只在代码中插入指令,编程人员可以循序渐进地逐步更改代码, 这有助于他们维持串行一致性。 当代码在单路处理器上执行时,最终结果与未经修改的源代码相同。 OpenMP 是一款可支持多个平台和操作系统的单一源代码解决方案。 编程人员不必费尽心思判断合适的内核数量,因为 OpenMP 运行时能够自动选择正确的内核数。
除了编程人员最常使用的循环级并行化结构外,OpenMP 3.0 还集成了一个新的任务级并行化结构,显著简化了对函数进行并行化的流程。 该任务模型可对带有不规则动态数据结构模式或诸如递归等不易并行化的复杂控制结构的程序进行并行处理。 任务编译指示在并行区域环境中执行,将创建显式任务。 如果在并行区域内遇到词法域编译指示,那么该任务块中的代码将概念性地排成一队,等待由执行并行区域的线程组中的一个线程执行。 为保留顺序语义,需确保并行区域内所有排队等待的任务在并行区域结束处均可完成。 编程人员应确保结构中不存在任何依赖关系,并且依赖关系已在显式任务之间,以及显式任务内外的代码之间的实现了适当同步化。
例如:
#pragma omp parallel #pragma omp single { for(int i = 0; i < size; i++) { #pragma omp task setQueen (new int[size], 0, i, myid); } }
Win32* 线程 API 和 Pthreads*
在某些情况下,开发人员更偏爱本地线程 API 的灵活性。 和本文之前讨论的线程提取方法相比,该方法的主要优势在于用户对线程的控制力度更高。 然而,采用该方法实施指定解决方案所需要的代码数量更多,因为编程人员必须执行所有单调乏味的线程实施任务,例如创建、调度、同步、本地存储、负载平衡和销毁等,而这些任务在其它方法中均由运行时系统来处理。 此外,编程人员必须确定可用内核的数量,这会影响到要创建的正确线程数。 该任务执行起来可能非常复杂,尤其是对独立于平台的解决方案而言。
例如:
void run_threaded_loop (int num_thr, size_t size, int _queens[]) { HANDLE* threads = new HANDLE[num_thr]; thr_params* params = new thr_params[num_thr]; for (int i = 0; i < num_thr; ++i) { // Give each thread equal number of rows params[i].start = i * (size / num_thr); params[i].end = params[i].start + (size / num_thr); params[i].queens = _queens; // Pass argument-pointer to a different // memory for each thread's parameter to avoid data races threads[i] = CreateThread (NULL, 0, run_solve, static_cast<void *> (¶ms[i]), 0, NULL); } // Join threads: wait until all threads are done WaitForMultipleObjects (num_thr, threads, true, INFINITE); // Free memory delete[] params; delete[] threads; }
线程库
在应用中添加并行性的另一种方法是使用线程库,如英特尔® 数学核心函数库(英特尔® MKL,并非英特尔 Parallel Composer 的组成部分)和英特尔® 高性能多媒体函数库(英特尔® IPP)。 英特尔® MKL 使用 OpenMP 进行线程化处理,能够提供高度优化的线程化数学例程,可最大限度提升性能。 若要使用线程化英特尔 MKL 函数,只需将 OMP_NUM_THREADS 环境变量设定为大于 1 的值即可。 英特尔 MKL 带有内部阀值,用于自行判断执行并行还是串行计算,或者编程人员还可以使用 OpenMP API 手动设置阀值,具体说是 omp_set_num_threads 函数。 在线技术说明介绍了有关 MKL 并行性的更多详细信息(面向 Windows* 的 MKL 11.0, 英特尔® MKL 10.x 线程化)。
英特尔® IPP 是一个面向多核技术的扩展函数库,其中包含众多针对多媒体数据处理和通信应用高度优化的软件函数。 英特尔 IPP 同样使用 OpenMP 进行多线程处理。 在线技术说明提供了关于 IPP 线程化与 OpenMP 支持的更多信息。
英特尔® 编译器还提供了一项 STL valarray实施,使用英特尔 IPP 为数学和超越运算提供数据并行性能。 C++ valarray 模板类由支持高性能计算的数组运算组成。 这些运算经过专门设计,可充分利用诸如矢量化等低级硬件功能。 英特尔的 valarray实施提供了多个面向英特尔 IPP 实现优化的 valarray运算版本,采用优化的替换 valarray头文件,且无需对源代码进行任何形式的更改。 若要利用英特尔优化性能头文件改善 valarray循环,请使用/Quse-intel-optimized-headers编译器选项。
自动并行化
自动并行化是英特尔 C++ 编译器的一个特性。 在自动并行化模式下,编译器将自动检测程序中固有的并行性。 该自动并行化工具可分析应用源代码中各个循环的数据流,并为可安全、高效地并行执行的循环生成多线程代码。 如果存在数据依赖关系,可能需要重构循环,以实现循环的自动并行化。
在自动并行化模式中,所有并行化决策均由编译器制定,开发人员根本无法控制哪些循环将实现并行化。 自动并行化还可与 OpenMP 结合使用,以实现更高的性能。 结合使用 OpenMP=20 和自动并行化功能时,OpenMP 将用来对含 OpenMP 指令的循环进行并行化处理,自动并行化功能则将用来对非 OpenMP 循环进行并行化处理。 自动并行化是通过 /Qparallel 编译器选项实现的。
例如:
#define N 10000 float a[N], b[N], c[N]; void f1() { for (int i = 1; i < N; i++) c[i] = a[i] + b[i]; }> icl /c /Qparallel par1.cpp par1.cpp(5): (col. 4) remark: LOOP WAS AUTO-PARALLELIZED.
默认情况下,自动并行化工具将报告哪些循环已成功经过自动并行化。 使用 /Qpar-report[n]选项,其中 n 代表 0 和 3 之间的数值,自动并行化工具可报告已实现自动并行化以及无法进行自动并行化的循环的诊断信息。 例如,/Qpar-report3要求自动并行化工具报告成功和未成功实现自动并行化的循环的诊断信息,以及任何经验证或假设可能阻碍自动并行化进程的依赖关系。 诊断信息可帮助开发人员重构循环,以成功实现自动并行化。
自动矢量化
矢量化是一项用来在英特尔处理器上优化循环性能的技巧。 由矢量化技巧定义的并行性基于通过处理器 SIMD硬件实现的矢量级并行性 (VLP)。 英特尔 C++ 编译器中的自动矢量化工具可自动检测程序中可并行执行的低级别运算,然后转换顺序代码,在一次运算中处理 1、2、4、8 或最多 16 字节数据元素,在未来处理器中可扩展至 32 和 64 字节。 循环必须是独立的,只有这样编译器才可对它们进行自动矢量化处理。 自动矢量化功能可与其它线程级并行化技巧如自动并行化和 OpenMP 一起使用。 大部分浮点应用和部分整数应用均可通过矢量化获得巨大优势。 默认的矢量化级别是 /arch:SSE2,可为英特尔® SIMD 流指令扩展 2(英特尔® SSE2)生成代码。 如果希望对默认目标之外的其它对象进行自动矢量化处理,请使用 /arch(如 /arch:SSE4.1) 或 /Qx(如 /QxSSE4.2, QxHost)编译器选项。
下图左侧展示了未实现矢量化的循环迭代串行执行情况,其中 SIMD 寄存器的下半部分并未得到利用。 右侧的矢量化版本则显示,每个循环迭代均并行添加了四个 A、B 数组元素,充分利用了 SIMD 寄存器的全宽。
图 1. 实现矢量化和未实现矢量化的循环迭代。
例如:
#define N 10000 float a[N], b[N], c[N]; void f1() { for (int i = 1; i < N; i++) c[i] = a[i] + b[i]; }> icl /c /QxSSE4.2 par1.cpp par1.cpp(5): (col. 4) remark: LOOP WAS VECTORIZED.
默认情况下,矢量化工具将报告实现矢量化的循环。 使用 /Qvec-report[n] 选项,其中 n 代表 0 和 5 之间的数值,自动矢量化工具可报告已实现矢量化以及未实现矢量化的循环的诊断信息。 例如,/Qvec-report5 选项要求矢量化工具报告未实现矢量化的循环以及其中的原因。 诊断信息可帮助开发人员重构循环,以成功实现矢量化。
不同方法利弊权衡
不同并行化方法可根据提取、控制和简易性进行分类。 英特尔 TBB 和 API 模型没有具体的编译器支持要求,但英特尔® CilkTM Plus 和 OpenMP 并非如此。使用英特尔 Cilk Plus 和 OpenMP 时,要求使用可识别 Cilk Plus 关键词和数组语句,以及 OpenMP 指令的编译器。 基于 API 的模型要求编程人员手动将当前的并行任务分配给各个线程。 线程间不存在显式父子关系,所有线程全部属于同一级别。 在这些模型中,编程人员可以控制线程创建、管理和同步化流程的所有低级别因素。 这一灵活性是基于数据库的线程化方法的主要优势。 但不足之处在于,要获得该灵活性,必须对代码进行大幅度修改,另外还必须执行大量其它编码任务。 性能调谐工作通常无法根据不同的内核数量或操作系统版本进行扩展或缩减。 当前的并行化任务必须压缩至可映射至线程的函数之中。 另一个不足之处是,大多数线程 API 均使用鲜为人知的调用协定,并且仅接受一个变元。 因此,对更适合 C 语言而不是面向对象的 C++ 语言的编程设计而言,编程人员通常需要修改可能破坏编程设计抽象的函数原型和数据结构。
作为一种基于编译器的线程化方法,OpenMP 为基本线程库提供了一个高级别接口。 借助 OpenMP,编程人员可使用 OpenMP 指令向编译器描述并行性。 该方法消除了显式线程化方法的大部分复杂性,因为编译器会处理相关细节。 采用增量式并行化方法,应用的串行结构将始终完整无缺,无需对源代码进行大幅度的修改。 非 OpenMP 编译器则完全忽视 OpenMP 指令,保持基本串行代码原封不动。
然而,使用 OpenMP 时,编程人员会丢失许多对线程的细微控制。 此外,OpenMP 不支持编程人员为线程设定优先级别,或执行基于事件的或进程间同步化操作。 OpenMP 是一个 fork-join 线程化模型,线程间存在着显式主从关系。 这些特征缩小了 OpenMP 的适用范围。 总的来说,OpenMP 最适合表达数据并行性,而显式线程 API 方法则最适用于函数分解。 OpenMP 以它的循环结构和 C 代码支持而闻名,但并没有为 C++ 编程带来任何具体优势。 OpenMP 3.0 支持任务指派,增加了不规则结构支持,例如 while 循环和递归结构,进一步扩展了 OpenMP 的适用范围。 但是,由于对 C++ 的支持级别极低,OpenMP 目前仍只会让人联想到普通的 C 和 FORTRAN 语言编程。
英特尔 ArBB 提供一般化的矢量并行编程解决方案,使应用开发人员可以独立于特定低级并行化机制或硬件架构。 英特尔 ArBB 使用 C++ 语言扩展以兼容所有标准编译器和 IDE,且不会受限于某种特定编译器。 ArBB 使用条件:
- 能够自然地以数据并行形式表达算法:
- 数组运算
- 以并行方式用于所有数组元素的初等函数
- 您希望编译器决定内核、线程和 SIMD 执行资源的最佳用途
- 您对“一次编码,随处运行”部署模式(基于 JIT 编译)感兴趣
- 您对确定性执行感兴趣,并让英特尔 ArBB 运行时管理内存空间使用
英特尔 Cilk Plus 以 C 和 C++ 语言的形式支持数据和任务并行性。 通过其三个简单关键词提供简单 fork-join 并行性,Cilk Plus 是为现有串行程序添加并行性的最简单的方法。 在此处介绍的并行模式中,它调用并行线程时的开销最低。 非 Cilk 编译器只需使用预处理器将关键词替换成串行对应词,即可忽略 cilk 关键词(为此提供头文件)。 但是不能简单地删除数组符号。 与无法自我组合的 OpenMP(比如嵌套 OpenMP)和其他线程模型不同,英特尔 Cilk Plus 能够与英特尔 TBB 和英特尔 ArBB 组合,不会导致过渡订阅线程。 这样有助于编程人员在需要其他并行结构和并行数据结构(比如区域锁、并行散列等)时,将 Cilk Plus 用于大多数代码,并使用英特尔 TBB 实施并行性。
英特尔 TBB 支持使用 C++ 代码(如 STL)进行一般性可扩展并行编程。 它没有特殊的语言或编译器要求。 如果编程人员需要一种灵活、高级别的并行化方法,与某个抽象或面向对象的一般方法完美结合,那么英特尔 TBB 将是他们的理想选择。 英特尔 TBB 使用面向通用并行迭代模式的模板,支持带嵌套式并行处理能力的可扩展数据并行编程。 与 API 方法不同,编程人员需要指定任务而不是线程,并且函数库可使用英特尔 TBB 运行时高效地将任务指派给不同线程。 英特尔 TBB 调度程序更倾向于使用一种自动化分治法来进行调度。 它可执行任务窃取操作,将任务从负载较高的内核迁移至闲置内核。 与 OpenMP 相比,该方法在英特尔 TBB 中实施,支持由开发人员定义的不局限于自带类型的并行性结构。
下表对英特尔 Parallel Composer 中的不同线程化技巧进行了对比:
方法 | 描述 | 优势 | 局限性 |
显式线程 API | 低级别 API 如 Win32* 线程 API 和用于低级别多线程编程的 Pthreads* |
|
|
OpenMP* (通过 /Qopenmp编译器选项实现) | OpenMP.org制定的一项规范,用于支持使用 API 和编译器指令在 C/C++ 和 Fortran 环境下进行共享内存并行编程 |
| 用户对线程的控制力度不高,例如不能设置线程优先级或执行基于事件的或进程间同步化操作
|
英特尔® Cilk™ Plus | 面向 C 和 C++ 的新关键词(cilk spawn, cilk sync, cilk for)、可避免竞跑情况的 reducer,以及可充分利用矢量化的数组符号。 |
|
|
英特尔® 线程构建模块 | 英特尔 C++ 运行时库可提供并行算法和并行数据结构,消除单调乏味的线程实施工作,进而简化线程化流程,提高性能。 |
|
|
自动并行化 (通过 /Qparallel编译器选项实现) | 英特尔® C++ 编译器的一项功能,用于自动对程序中没有循环传递相关性的循环进行并行处理 |
| 只适用于编译器在静态状态下通过数据依赖关系和别名分析断定可实现并行处理的循环 |
自动矢量化 (通过/arch:和 /Qx选项实现) | 在英特尔® 处理器上通过矢量层并行化优化循环性能的技巧,将顺序指令转换为每次可运算多个数据元的SIMD指令 |
| 如果使用了面向特定处理器的选项,最终生成的代码可能无法在所有处理器上成功执行 |
“并行解决 n 皇后问题”是一篇动手练习培训文章,要求读者使用本文中讨论的各项并行化技巧制定一个并行解决方案来解决 N 皇后问题( 八皇后谜题的更常见版本)。 英特尔® C++ 编译器安装文件夹下的“Samples”文件夹中提供有更多的示例。
更多资源
现代代码社区
关于英特尔编译器的一般信息、文档、白皮书和知识库
软件优化手册(第二版)英特尔架构高性能方案
英特尔开发人员专区论坛
有关 OpenMP 的其他信息,包括完整规范和指令列表
英特尔® 线程构建模块
面向开放源代码的英特尔线程构建模块
James Reinders, 英特尔线程构建模块: 针对多核处理器并行机制配置 C++, O'Reilly Media, Inc. Sebastopol, CA, 2007 年。