说明:
下载User Guide: http://software.intel.com/zh-cn/forums/showthread.php?t=77996&o=a&s=lr(Cilk_User_Guide.pdf)
主要是对该用户指南(中文版)的一些学习笔记和简化并更加自己的理解添加一些代码示例,可以参考原文档获取更多细节。
cilk的主要内容就是三个关键字cilk_spawn,cilk_sync,cilk_for,但是除此之外,需要考虑的一个问题是数据竞争的相关问题,对于任何并行编程多线程编程,数据竞争都是需要考虑的一个问题。那么,针对cilk程序的数据竞争,该如何处理呢?
cilk程序的数据竞争和其他并行编程数据竞争一样,解决方法无非就是:重新构造代码、修改算法、使用局部变量替代全局变量、加锁等。其中,大部分最终需要通过加锁解决数据竞争。cilk本身没有提供加锁的数据结构,但是,cilk能识别其它框架的一些加锁机制。
Cilk可以识别下列加锁机制:
1. Intel® Threading Building Blocks库提供了tbb::mutex用于创建临界区代码。临界区代码中对共享内存及其它共享资源的更新与访问是安全的。Intel® Parallel Studio工具可以识别该加锁机制,对于通过tbb::mutex进行保护的内存访问不会报告数据竞争。
2. Windows*操作系统:CRITICAL_SECTION对象的功能和tbb::mutex对象基本相同。对于通过EnterCriticalSection()、TryEnterCriticalSection()或LeaveCriticalSection()进行保护的访问,Intel® Parallel Studio工具不会报告数据竞争。
3. Linux*操作系统:Posix* Pthread互斥锁(pthread_mutex_t)的功能和tbb::mutex基本相同。对于通过pthread_mutex_lock()、 pthread_mutex_trylock()或pthread_mutex_unlock()进行保护的访问,Intel® Parallel Studio工具不会报告数据竞争。
4. Intel® Parallel Studio工具可以识别原子机器指令, C/C++程序员可以通过编译器基本函数来使用这些指令。
另外,对于死锁等在cilk中仍然和其他并行程序理解类似。对于cilk程序的性能,除了粒度可以影响之外,一般的并行程序的相关问题如锁竞争、高速缓存效率和内存带宽、伪共享、原子操作等等都可能同样影响cilk程序的性能。
除了上面的修改代码和加锁解决数据竞争的方法,还有一种特殊的解决数据竞争的方法:reducer。当然,这种方法本身也只能适用于一些特殊的数据竞争。关于reducer的功能理解,和OpenMP的reduction子句的功能是一样的,参考:http://blog.csdn.net/gengshenghong/article/details/7000685。当然,这是说功能上是一致的,在cikl中reducer是一种数据结构(数据类型),而openmp的reduction是一个子句。
Reducers有下面这些重要属性:
Reducers允许无竞争的可靠存取非本地变量。
Reducers不需要加锁,因而避免了由于对非本地变量加锁而带来的锁竞争问题, 以及由此而引起的无法并行的问题。
在正确的定义和使用情况下, Reducers保留了串行语义。 使用Reducers的Cilk程序的结果与串行版本的结果是一致的, 该结果不依赖于目标机器的处理器数目, 也不依赖于工作线程的调度。 Reducers的使用不需要对现存的代码结构做明显的修改。
Reducers的实现是高效的。
与定义在控制结构比如循环上的实现不一样, Reducers的使用不依赖于程序的控制结构。
参考http://software.intel.com/zh-cn/blogs/2010/06/25/intel-cilk-plus-reducer/和user guide理解reducer视图。
总体来说,个人觉得,reducer是很容易理解的,主要就是用于一些“迭代”操作,如“迭加”,“迭乘”等。
下面是一个求和的例子,用于理解reducer的使用:
// File: test1.cpp #include <stdio.h> #include <cilk/cilk.h> #include <cilk/reducer_opadd.h> #define N 1000000 cilk::reducer_opadd<unsigned long long> sum; // 不需要初始化为0,如果需要进行初始化其他值,需要自己修改代码处理。 unsigned long long sum0=0, sum1=0; int main() { // case1 for(int i=0;i < N;i++) { sum0 = sum0 + i; } printf("Correct Sum is %d\n",sum0); // case2 cilk_for(int i=0;i < N;i++) { sum1 = sum1 + i; } printf("No Reducer Sum is %d\n",sum1); // case3 cilk_for(int i=0;i < N;i++) { sum = sum + i; } printf("Reducer Sum is %d\n",sum.get_value()); return 0; }