diff options
Diffstat (limited to 'courseNotes/parallel.html')
-rw-r--r-- | courseNotes/parallel.html | 169 |
1 files changed, 79 insertions, 90 deletions
diff --git a/courseNotes/parallel.html b/courseNotes/parallel.html index ebd09d4..0c822fd 100644 --- a/courseNotes/parallel.html +++ b/courseNotes/parallel.html | |||
@@ -6,25 +6,27 @@ | |||
6 | <meta name="generator" content="pandoc" /> | 6 | <meta name="generator" content="pandoc" /> |
7 | <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> | 7 | <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> |
8 | <title>并行计算大法好</title> | 8 | <title>并行计算大法好</title> |
9 | <script src="none" type="text/javascript"></script> | ||
9 | <link rel="stylesheet" href="https://www.qin-juan-ge-zhu.top/common/CSS/pandoc.css"> | 10 | <link rel="stylesheet" href="https://www.qin-juan-ge-zhu.top/common/CSS/pandoc.css"> |
10 | <script type="text/javascript" src="https://hl.qin-juan-ge-zhu.top/myset/myhighlight.js"></script> | ||
11 | <script type="text/javascript" src="https://www.qin-juan-ge-zhu.top/common/script4code.js"></script> | 11 | <script type="text/javascript" src="https://www.qin-juan-ge-zhu.top/common/script4code.js"></script> |
12 | <script type="text/javascript" async | 12 | <script type="text/javascript" async |
13 | src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script> | 13 | src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script> |
14 | <script type="text/x-mathjax-config"> | 14 | <script type="text/x-mathjax-config"> |
15 | MathJax.Hub.Config({ | 15 | MathJax.Hub.Config({ |
16 | tex2jax: { | 16 | tex2jax: { |
17 | inlineMath: [['$','$'], ['\\(','\\)']], | 17 | inlineMath: [["$","$"], ["\\(","\\)"]], |
18 | processEscapes: true | 18 | processEscapes: true |
19 | } | 19 | } |
20 | }); | 20 | }); |
21 | </script> | 21 | </script> |
22 | </head> | 22 | </head> |
23 | 23 | ||
24 | <body> | 24 | <body> |
25 | <div class="pandoc"> | 25 | <div class="pandoc"> |
26 | <div class="main"> | 26 | <div class="main"> |
27 | <p class="title">并行计算大法好</p> | 27 | <header id="title-block-header"> |
28 | <p class="title">并行计算大法好</p> | ||
29 | </header> | ||
28 | <h1 id="基于共享内存的并行编程">基于共享内存的并行编程</h1> | 30 | <h1 id="基于共享内存的并行编程">基于共享内存的并行编程</h1> |
29 | <h2 id="pthreads的多线程并行"><code>Pthreads</code>的多线程并行</h2> | 31 | <h2 id="pthreads的多线程并行"><code>Pthreads</code>的多线程并行</h2> |
30 | <h3 id="简介">简介</h3> | 32 | <h3 id="简介">简介</h3> |
@@ -39,7 +41,7 @@ | |||
39 | <li>条件变量</li> | 41 | <li>条件变量</li> |
40 | </ul> | 42 | </ul> |
41 | <h3 id="线程管理">线程管理</h3> | 43 | <h3 id="线程管理">线程管理</h3> |
42 | <pre><code>#include <stdio.h> | 44 | <pre><code class="language-c">#include <stdio.h> |
43 | #include <stdlib.h> | 45 | #include <stdlib.h> |
44 | #include <pthread.h> | 46 | #include <pthread.h> |
45 | 47 | ||
@@ -102,17 +104,18 @@ int main() | |||
102 | </ul> | 104 | </ul> |
103 | <h4 id="忙等待">忙等待</h4> | 105 | <h4 id="忙等待">忙等待</h4> |
104 | <p>可以通过空循环的方法实现当一个线程在用的时候另一个线程禁止访问。</p> | 106 | <p>可以通过空循环的方法实现当一个线程在用的时候另一个线程禁止访问。</p> |
105 | <p>空口白话不好使,看代码吧,利用泰勒公式求$\pi$的值。</p> | 107 | <p>空口白话不好使,看代码吧,利用泰勒公式求<span class="math inline">\(\pi\)</span>的值。</p> |
106 | <p>已知:</p> | 108 | <p>已知:</p> |
107 | <ul> | 109 | <ul> |
108 | <li>$arctan(1)=\frac \pi 4$</li> | 110 | <li><span class="math inline">\(arctan(1)=\frac \pi 4\)</span></li> |
109 | <li>$arctan(x)=\Sigma_{i=0}^{+\infty}(-1)^i \frac 1 {2x+1}$,收敛域为$[-1, 1]$</li> | 111 | <li><span class="math inline">\(arctan(x)=\Sigma_{i=0}^{+\infty}(-1)^i \frac 1 {2x+1}\)</span>,收敛域为<span |
112 | class="math inline">\([-1,1]\)</span></li> | ||
110 | </ul> | 113 | </ul> |
111 | <blockquote> | 114 | <blockquote> |
112 | <p>哪个大佬知道收敛更快的算法啊,这个办法<strong>收敛实在是太慢辣</strong>!</p> | 115 | <p>哪个大佬知道收敛更快的算法啊,这个办法<strong>收敛实在是太慢辣</strong>!</p> |
113 | </blockquote> | 116 | </blockquote> |
114 | <p>编程如下:</p> | 117 | <p>编程如下:</p> |
115 | <pre><code>#include <stdio.h> | 118 | <pre><code class="language-c">#include <stdio.h> |
116 | #include <stdlib.h> | 119 | #include <stdlib.h> |
117 | #include <pthread.h> | 120 | #include <pthread.h> |
118 | 121 | ||
@@ -171,7 +174,7 @@ int main() | |||
171 | <p>上述<strong>通过循环的办法实现的忙等待,优点在于实现比较简单好理解,缺点在于空循环浪费 CPU | 174 | <p>上述<strong>通过循环的办法实现的忙等待,优点在于实现比较简单好理解,缺点在于空循环浪费 CPU |
172 | 资源,且当线程数较多时,线程切换的开销也会增大</strong>。因此我们可以调用<code>Pthreads</code>提供的互斥锁来实现临界区的功能。互斥锁是一个变量,通过调用函数来实现锁定临界区实现忙等待。 | 175 | 资源,且当线程数较多时,线程切换的开销也会增大</strong>。因此我们可以调用<code>Pthreads</code>提供的互斥锁来实现临界区的功能。互斥锁是一个变量,通过调用函数来实现锁定临界区实现忙等待。 |
173 | </p> | 176 | </p> |
174 | <pre><code>#include <stdio.h> | 177 | <pre><code class="language-c">#include <stdio.h> |
175 | #include <stdlib.h> | 178 | #include <stdlib.h> |
176 | #include <pthread.h> | 179 | #include <pthread.h> |
177 | 180 | ||
@@ -260,7 +263,7 @@ int main() | |||
260 | <li>信号量为 0 时,试图访问共享资源的线程将处于等待状态</li> | 263 | <li>信号量为 0 时,试图访问共享资源的线程将处于等待状态</li> |
261 | </ul> | 264 | </ul> |
262 | <p>需要注意的是,上述的信号量只是一种管理方法,与<code>Pthreads</code>中的信号量似乎区别不小,这一点从<code>sem_t</code>的大小上就能看出来。凑合看吧,知道原理就行。</p> | 265 | <p>需要注意的是,上述的信号量只是一种管理方法,与<code>Pthreads</code>中的信号量似乎区别不小,这一点从<code>sem_t</code>的大小上就能看出来。凑合看吧,知道原理就行。</p> |
263 | <pre><code>#include <semaphore.h> // 信号量头文件 | 266 | <pre><code class="language-c">#include <semaphore.h> // 信号量头文件 |
264 | 267 | ||
265 | // 定义信号量 | 268 | // 定义信号量 |
266 | sem_t sem; | 269 | sem_t sem; |
@@ -296,7 +299,7 @@ int sem_destroy(sem_t *sem);</code></pre> | |||
296 | <li>主线程将共享数组初始化为<code>NULL</code></li> | 299 | <li>主线程将共享数组初始化为<code>NULL</code></li> |
297 | <li>信号量初始化为 0</li> | 300 | <li>信号量初始化为 0</li> |
298 | </ul> | 301 | </ul> |
299 | <pre><code>#include <stdio.h> | 302 | <pre><code class="language-c">#include <stdio.h> |
300 | #include <pthread.h> | 303 | #include <pthread.h> |
301 | #include <semaphore.h> | 304 | #include <semaphore.h> |
302 | #define MSG_MAX_LEN 50 | 305 | #define MSG_MAX_LEN 50 |
@@ -360,7 +363,7 @@ int main() | |||
360 | <h3 id="路障">路障</h3> | 363 | <h3 id="路障">路障</h3> |
361 | <p>路障也称同步点,指线程到达此处进入阻塞状态,等所有进程到达后才能继续进行,主要应用于程序计时/调试等。</p> | 364 | <p>路障也称同步点,指线程到达此处进入阻塞状态,等所有进程到达后才能继续进行,主要应用于程序计时/调试等。</p> |
362 | <p>路障使用有以下几步:</p> | 365 | <p>路障使用有以下几步:</p> |
363 | <pre><code>#include <pthread.h> | 366 | <pre><code class="language-c">#include <pthread.h> |
364 | 367 | ||
365 | // 定义路障 | 368 | // 定义路障 |
366 | pthread_barrier_t barrier; | 369 | pthread_barrier_t barrier; |
@@ -379,7 +382,7 @@ int pthread_barrier_wait(pthread_barrier_t *barrier); | |||
379 | // 销毁路障 | 382 | // 销毁路障 |
380 | int pthread_barrier_destroy(pthread_barrier_t *barrier);</code></pre> | 383 | int pthread_barrier_destroy(pthread_barrier_t *barrier);</code></pre> |
381 | <p>示例程序如下:</p> | 384 | <p>示例程序如下:</p> |
382 | <pre><code>#include <stdio.h> | 385 | <pre><code class="language-c">#include <stdio.h> |
383 | #include <stdlib.h> | 386 | #include <stdlib.h> |
384 | #include <pthread.h> | 387 | #include <pthread.h> |
385 | #include <time.h> | 388 | #include <time.h> |
@@ -427,7 +430,7 @@ int main() | |||
427 | }</code></pre> | 430 | }</code></pre> |
428 | <h3 id="条件变量">条件变量</h3> | 431 | <h3 id="条件变量">条件变量</h3> |
429 | <p>条件变量使线程在特定条件或事件之前处于挂起状态。</p> | 432 | <p>条件变量使线程在特定条件或事件之前处于挂起状态。</p> |
430 | <pre><code>#include <pthread.h> | 433 | <pre><code class="language-c">#include <pthread.h> |
431 | 434 | ||
432 | // 定义条件变量 | 435 | // 定义条件变量 |
433 | pthread_cond_t cond; | 436 | pthread_cond_t cond; |
@@ -456,7 +459,7 @@ int pthread_cond_broadcast(pthread_cond_t *cond); | |||
456 | */ | 459 | */ |
457 | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);</code></pre> | 460 | int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);</code></pre> |
458 | <p>示例程序,这个程序真没看明白:</p> | 461 | <p>示例程序,这个程序真没看明白:</p> |
459 | <pre><code>#include <stdio.h> | 462 | <pre><code class="language-c">#include <stdio.h> |
460 | #include <stdlib.h> | 463 | #include <stdlib.h> |
461 | #include <pthread.h> | 464 | #include <pthread.h> |
462 | #include <unistd.h> | 465 | #include <unistd.h> |
@@ -517,7 +520,7 @@ int main() | |||
517 | }</code></pre> | 520 | }</code></pre> |
518 | <h3 id="读写锁">读写锁</h3> | 521 | <h3 id="读写锁">读写锁</h3> |
519 | <p>读写锁在互斥量的基础上,<strong>把对共享资源的访问分为读者和写者</strong>,读者只能读、写者只能写,读者之间不互斥,写者之间互斥,读者和写者之间互斥。</p> | 522 | <p>读写锁在互斥量的基础上,<strong>把对共享资源的访问分为读者和写者</strong>,读者只能读、写者只能写,读者之间不互斥,写者之间互斥,读者和写者之间互斥。</p> |
520 | <pre><code>#include <pthread.h> // 哈哈,没想到吧 | 523 | <pre><code class="language-c">#include <pthread.h> // 哈哈,没想到吧 |
521 | 524 | ||
522 | // 定义读写锁 | 525 | // 定义读写锁 |
523 | pthread_rwlock_t rwlock; | 526 | pthread_rwlock_t rwlock; |
@@ -541,7 +544,7 @@ int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); | |||
541 | // 销毁 | 544 | // 销毁 |
542 | int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);</code></pre> | 545 | int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);</code></pre> |
543 | <p>示例程序:</p> | 546 | <p>示例程序:</p> |
544 | <pre><code>#include <stdio.h> | 547 | <pre><code class="language-c">#include <stdio.h> |
545 | #include <stdlib.h> | 548 | #include <stdlib.h> |
546 | #include <string.h> | 549 | #include <string.h> |
547 | #include <pthread.h> | 550 | #include <pthread.h> |
@@ -646,8 +649,9 @@ int main(int argc,char *argv[]) | |||
646 | 采用<code>Fork-Join</code>的并行编程方式,开始于一个单独的主线程,一直串行执行(串行域),直到遇见并行域;在并行域中,根据指定的线程数,多线程并行执行;并行域结束后,所有线程汇合,继续串行执行。如此在串行域和并行域中循环往复,直到程序结束。 | 649 | 采用<code>Fork-Join</code>的并行编程方式,开始于一个单独的主线程,一直串行执行(串行域),直到遇见并行域;在并行域中,根据指定的线程数,多线程并行执行;并行域结束后,所有线程汇合,继续串行执行。如此在串行域和并行域中循环往复,直到程序结束。 |
647 | </p> | 650 | </p> |
648 | <p>需要注意的是,在并行域中,可以划分出更小的并行域,也就是一个线程再次划分为多个线程执行。</p> | 651 | <p>需要注意的是,在并行域中,可以划分出更小的并行域,也就是一个线程再次划分为多个线程执行。</p> |
652 | <img src="https://www.qin-juan-ge-zhu.top/images/courseNotes/parallel_1.png"> | ||
649 | <h3 id="使用指南">使用指南</h3> | 653 | <h3 id="使用指南">使用指南</h3> |
650 | <pre><code>// 头文件必不可少 | 654 | <pre><code class="language-c">// 头文件必不可少 |
651 | #include <omp.h> | 655 | #include <omp.h> |
652 | 656 | ||
653 | // 指定线程数,这是一个函数,参数就是线程数 | 657 | // 指定线程数,这是一个函数,参数就是线程数 |
@@ -666,17 +670,14 @@ omp_set_num_threads(4); | |||
666 | // some codes here. | 670 | // some codes here. |
667 | }</code></pre> | 671 | }</code></pre> |
668 | <p>编译的时候,使用<code>-fopenmp</code>选项是万万不能忘记的:</p> | 672 | <p>编译的时候,使用<code>-fopenmp</code>选项是万万不能忘记的:</p> |
669 | <div class="sourceCode" id="cb14"> | 673 | <pre><code class="language-bash">gcc -g -o test test.c -fopenmp</code></pre> |
670 | <pre | ||
671 | class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true"></a><span class="fu">gcc</span> -g -o test test.c -fopenmp</span></code></pre> | ||
672 | </div> | ||
673 | <p>在 OpenMP 程序编写中,需要注意以下几点:</p> | 674 | <p>在 OpenMP 程序编写中,需要注意以下几点:</p> |
674 | <ul> | 675 | <ul> |
675 | <li>通常采用增量并行,也局势每次只对部分代码并行化,这样可以<strong>逐步改造</strong>,方便调试</li> | 676 | <li>通常采用增量并行,也局势每次只对部分代码并行化,这样可以<strong>逐步改造</strong>,方便调试</li> |
676 | <li>OpenMP 指令<strong>区分大小写</strong></li> | 677 | <li>OpenMP 指令<strong>区分大小写</strong></li> |
677 | </ul> | 678 | </ul> |
678 | <p>先来一段小实例:</p> | 679 | <p>先来一段小实例:</p> |
679 | <pre><code>#include <stdio.h> | 680 | <pre><code class="language-c">#include <stdio.h> |
680 | #include <omp.h> | 681 | #include <omp.h> |
681 | 682 | ||
682 | int main() | 683 | int main() |
@@ -688,7 +689,7 @@ int main() | |||
688 | return 0; | 689 | return 0; |
689 | }</code></pre> | 690 | }</code></pre> |
690 | <p>可以看到一般的结果为:</p> | 691 | <p>可以看到一般的结果为:</p> |
691 | <pre class="plain"><code>Hello World! I'm thread 1 | 692 | <pre><code class="language-plain">Hello World! I'm thread 1 |
692 | Hello World! I'm thread 2 | 693 | Hello World! I'm thread 2 |
693 | Hello World! I'm thread 3 | 694 | Hello World! I'm thread 3 |
694 | Hello World! I'm thread 5 | 695 | Hello World! I'm thread 5 |
@@ -772,7 +773,7 @@ Hello World! I'm the master thread</code></pre> | |||
772 | <p>再次说明,不支持 OpenMP 的编译器也能编译 OpenMP 程序,不过是忽略了这部分语句,也就是直接串行执行。</p> | 773 | <p>再次说明,不支持 OpenMP 的编译器也能编译 OpenMP 程序,不过是忽略了这部分语句,也就是直接串行执行。</p> |
773 | <h4 id="并行域指令">并行域指令</h4> | 774 | <h4 id="并行域指令">并行域指令</h4> |
774 | <p>并行域指令前边写了,就不废话了。需要注意的是结尾处有隐式同步(等待所有线程结束才进入下一个串行域)。另外,可用的字句包括以下几种:</p> | 775 | <p>并行域指令前边写了,就不废话了。需要注意的是结尾处有隐式同步(等待所有线程结束才进入下一个串行域)。另外,可用的字句包括以下几种:</p> |
775 | <pre><code>if(scalar-logical-expression) | 776 | <pre><code class="language-c">if(scalar-logical-expression) |
776 | num_threads(scalar-integer-expression) | 777 | num_threads(scalar-integer-expression) |
777 | default(shared|none) | 778 | default(shared|none) |
778 | private(list) | 779 | private(list) |
@@ -790,7 +791,7 @@ reduction(operator:list)</code></pre> | |||
790 | <li><code>task</code>指令</li> | 791 | <li><code>task</code>指令</li> |
791 | </ul> | 792 | </ul> |
792 | <h5 id="for-指令">for 指令</h5> | 793 | <h5 id="for-指令">for 指令</h5> |
793 | <pre><code>/* | 794 | <pre><code class="language-c">/* |
794 | * for指令,自动划分和分配循环任务 | 795 | * for指令,自动划分和分配循环任务 |
795 | * 需要注意,循环变量只能是整形或指针 | 796 | * 需要注意,循环变量只能是整形或指针 |
796 | */ | 797 | */ |
@@ -807,7 +808,7 @@ reduction(operator:list)</code></pre> | |||
807 | * nowait | 808 | * nowait |
808 | */</code></pre> | 809 | */</code></pre> |
809 | <p>示例程序:</p> | 810 | <p>示例程序:</p> |
810 | <pre><code>#include <omp.h> | 811 | <pre><code class="language-c">#include <omp.h> |
811 | #include <stdio.h> | 812 | #include <stdio.h> |
812 | #define N 10000 | 813 | #define N 10000 |
813 | 814 | ||
@@ -857,7 +858,7 @@ int main() | |||
857 | </li> | 858 | </li> |
858 | </ul> | 859 | </ul> |
859 | <p>OpenMP 提供了以下几种调度方式(<code>schedule</code>子句):</p> | 860 | <p>OpenMP 提供了以下几种调度方式(<code>schedule</code>子句):</p> |
860 | <pre><code>schedule(static,chunk_size) // 静态分配,chunk_size为任务块大小 | 861 | <pre><code class="language-c">schedule(static,chunk_size) // 静态分配,chunk_size为任务块大小 |
861 | schedule(dynamic,chunk_size) // 动态分配,chunk_size为任务块大小,按照先来先服务原则分配 | 862 | schedule(dynamic,chunk_size) // 动态分配,chunk_size为任务块大小,按照先来先服务原则分配 |
862 | schedule(guided,chunk_size) // 动态分配。任务块大小可变,先大后小,chunk_size为最小任务块大小 | 863 | schedule(guided,chunk_size) // 动态分配。任务块大小可变,先大后小,chunk_size为最小任务块大小 |
863 | schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`OMP_SCHEDULE`指定</code></pre> | 864 | schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`OMP_SCHEDULE`指定</code></pre> |
@@ -891,7 +892,7 @@ schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`O | |||
891 | <li>每个子任务<strong>只会</strong>被一个线程执行</li> | 892 | <li>每个子任务<strong>只会</strong>被一个线程执行</li> |
892 | <li>结尾处隐式同步,除非有子句<code>nowait</code></li> | 893 | <li>结尾处隐式同步,除非有子句<code>nowait</code></li> |
893 | </ul> | 894 | </ul> |
894 | <pre><code>/* | 895 | <pre><code class="language-c">/* |
895 | * 可用子句: | 896 | * 可用子句: |
896 | * private(list) | 897 | * private(list) |
897 | * firstprivate(list) | 898 | * firstprivate(list) |
@@ -926,7 +927,7 @@ schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`O | |||
926 | <li><code>copyprivate(list)</code></li> | 927 | <li><code>copyprivate(list)</code></li> |
927 | <li><code>nowait</code></li> | 928 | <li><code>nowait</code></li> |
928 | </ul> | 929 | </ul> |
929 | <pre><code>#pragma omp parallel | 930 | <pre><code class="language-c">#pragma omp parallel |
930 | { | 931 | { |
931 | // 代码 | 932 | // 代码 |
932 | #pragma omp single [clause [clause]...] | 933 | #pragma omp single [clause [clause]...] |
@@ -939,7 +940,7 @@ schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`O | |||
939 | <p>master | 940 | <p>master |
940 | 块仅由线程组之中的<strong>主线程执行</strong>,其他线程<strong>跳过并继续执行后续代码</strong>,即结尾处<strong>没有隐式同步</strong>。该结构通常用于 | 941 | 块仅由线程组之中的<strong>主线程执行</strong>,其他线程<strong>跳过并继续执行后续代码</strong>,即结尾处<strong>没有隐式同步</strong>。该结构通常用于 |
941 | I/O。注意其与 single 结构的区别。</p> | 942 | I/O。注意其与 single 结构的区别。</p> |
942 | <pre><code>#pragmaomp parallel private(tid) | 943 | <pre><code class="language-c">#pragmaomp parallel private(tid) |
943 | { | 944 | { |
944 | tid=omp_get_thread_num(); | 945 | tid=omp_get_thread_num(); |
945 | printf("Thread %d is here!\n",tid); | 946 | printf("Thread %d is here!\n",tid); |
@@ -952,7 +953,7 @@ schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`O | |||
952 | }</code></pre> | 953 | }</code></pre> |
953 | <h5 id="task-结构">task 结构</h5> | 954 | <h5 id="task-结构">task 结构</h5> |
954 | <p>task 指令主要用于不规则循环迭代(如<code>do-while</code>循环)和递归的函数调用。</p> | 955 | <p>task 指令主要用于不规则循环迭代(如<code>do-while</code>循环)和递归的函数调用。</p> |
955 | <pre><code>#pragma omp parallel | 956 | <pre><code class="language-c">#pragma omp parallel |
956 | { | 957 | { |
957 | #pragma omp single | 958 | #pragma omp single |
958 | { | 959 | { |
@@ -969,9 +970,9 @@ schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`O | |||
969 | }</code></pre> | 970 | }</code></pre> |
970 | <h4 id="变量作用域与属性">变量作用域与属性</h4> | 971 | <h4 id="变量作用域与属性">变量作用域与属性</h4> |
971 | <p>变量作用域可以通过如下方式修改:</p> | 972 | <p>变量作用域可以通过如下方式修改:</p> |
972 | <pre><code>default(shared|none)</code></pre> | 973 | <pre><code class="language-c">default(shared|none)</code></pre> |
973 | <p>作用域属性语句:</p> | 974 | <p>作用域属性语句:</p> |
974 | <pre><code>shared(varname,...) | 975 | <pre><code class="language-c">shared(varname,...) |
975 | private(varname,...)</code></pre> | 976 | private(varname,...)</code></pre> |
976 | <p>变量究竟应该是共享的还是私有的?</p> | 977 | <p>变量究竟应该是共享的还是私有的?</p> |
977 | <ul> | 978 | <ul> |
@@ -1011,8 +1012,7 @@ private(varname,...)</code></pre> | |||
1011 | <tr class="odd"> | 1012 | <tr class="odd"> |
1012 | <td style="text-align: left;">lastprivate(list)</td> | 1013 | <td style="text-align: left;">lastprivate(list)</td> |
1013 | <td style="text-align: left;">private 的扩展,推出并行域时,将制定的私有拷贝的“最后”值赋值给主线程变量。“最后”指循环的最后一次迭代、sections | 1014 | <td style="text-align: left;">private 的扩展,推出并行域时,将制定的私有拷贝的“最后”值赋值给主线程变量。“最后”指循环的最后一次迭代、sections |
1014 | 的最后一个 | 1015 | 的最后一个 section 等。可能会增加额外开销,一般不建议使用,可用共享变量等方式实现</td> |
1015 | section 等。可能会增加额外开销,一般不建议使用,可用共享变量等方式实现</td> | ||
1016 | <td></td> | 1016 | <td></td> |
1017 | </tr> | 1017 | </tr> |
1018 | <tr class="even"> | 1018 | <tr class="even"> |
@@ -1089,18 +1089,13 @@ private(varname,...)</code></pre> | |||
1089 | </tr> | 1089 | </tr> |
1090 | </tbody> | 1090 | </tbody> |
1091 | </table> | 1091 | </table> |
1092 | <p>我们仍然以计算$\pi$为例子:</p> | 1092 | <p>我们仍然以计算<span class="math inline">\(pi\)</span>为例子:</p> |
1093 | <!-- <p><br /><span class="math display">$$ | 1093 | <p><span class="math display">\[ |
1094 | \begin{align*} | ||
1095 | \pi = 4\Sigma_{i=0}^{+\infty}\frac {(-1)^i}{2i+1} | ||
1096 | \end{align*} | ||
1097 | $$</span><br /></p> --> | ||
1098 | <p><br />$$ | ||
1099 | \begin{align*} | 1094 | \begin{align*} |
1100 | \pi = 4\Sigma_{i=0}^{+\infty}\frac {(-1)^i}{2i+1} | 1095 | \pi = 4\Sigma_{i=0}^{+\infty}\frac {(-1)^i}{2i+1} |
1101 | \end{align*} | 1096 | \end{align*} |
1102 | $$<br /></p> | 1097 | \]</span></p> |
1103 | <pre><code>#include <stdio.h> | 1098 | <pre><code class="language-c">#include <stdio.h> |
1104 | #include <omp.h> | 1099 | #include <omp.h> |
1105 | #define NUM_OF_CYCLES 1000000000 | 1100 | #define NUM_OF_CYCLES 1000000000 |
1106 | 1101 | ||
@@ -1157,9 +1152,10 @@ int main() | |||
1157 | MPI 的支持。</p> | 1152 | MPI 的支持。</p> |
1158 | <p><strong>MPI 是一个库,不是一门语言</strong>,其最终目的是服务于进程间通信。</p> | 1153 | <p><strong>MPI 是一个库,不是一门语言</strong>,其最终目的是服务于进程间通信。</p> |
1159 | <h3 id="一般结构">一般结构</h3> | 1154 | <h3 id="一般结构">一般结构</h3> |
1160 | <p>MPI 程序的一般结构为: 包含MPI头文件-->初始化MPI环境-->信息交换处理及计算等-->退出MPI环境。</p> | 1155 | <p>MPI 程序的一般结构为:</p> |
1156 | <img src="https://www.qin-juan-ge-zhu.top/images/courseNotes/parallel_2.png"> | ||
1161 | <p>写程序罢。</p> | 1157 | <p>写程序罢。</p> |
1162 | <pre><code>// hello.c | 1158 | <pre><code class="language-c">// hello.c |
1163 | #include <stdio.h> | 1159 | #include <stdio.h> |
1164 | #include <mpi.h> // MPI的头文件 | 1160 | #include <mpi.h> // MPI的头文件 |
1165 | 1161 | ||
@@ -1172,16 +1168,14 @@ int main(int argc,char *argv[]) | |||
1172 | return 0; | 1168 | return 0; |
1173 | }</code></pre> | 1169 | }</code></pre> |
1174 | <p>编译运行:</p> | 1170 | <p>编译运行:</p> |
1175 | <div class="sourceCode" id="cb30"> | 1171 | <pre><code class="language-bash"># 编译MPI程序,需要专用的编译器mpicc |
1176 | <pre | 1172 | mpicc -O2 hello hello.c |
1177 | class="sourceCode bash"><code class="sourceCode bash"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true"></a><span class="co"># 编译MPI程序,需要专用的编译器mpicc</span></span> | 1173 | |
1178 | <span id="cb30-2"><a href="#cb30-2" aria-hidden="true"></a><span class="ex">mpicc</span> -O2 hello hello.c</span> | 1174 | # 运行的命令也比较特别 |
1179 | <span id="cb30-3"><a href="#cb30-3" aria-hidden="true"></a></span> | 1175 | mpirun -np 4 ./hello</code></pre> |
1180 | <span id="cb30-4"><a href="#cb30-4" aria-hidden="true"></a><span class="co"># 运行的命令也比较特别</span></span> | ||
1181 | <span id="cb30-5"><a href="#cb30-5" aria-hidden="true"></a><span class="ex">mpirun</span> -np 4 ./hello</span></code></pre> | ||
1182 | </div> | ||
1183 | <h3 id="mpi-通信器">MPI 通信器</h3> | 1176 | <h3 id="mpi-通信器">MPI 通信器</h3> |
1184 | <p>现在我们已经学会了$1+1=2$,让我们来<del>手搓一下$e^\pi$的值</del>看看第二个程序罢。</p> | 1177 | <p>现在我们已经学会了<span class="math inline">\(1+1=2\)</span>,让我们来<del>手搓一下<span |
1178 | class="math inline">\(e^{\pi}\)</span>的值</del>看看第二个程序罢。</p> | ||
1185 | <p>通信器/通信子是什么?</p> | 1179 | <p>通信器/通信子是什么?</p> |
1186 | <ul> | 1180 | <ul> |
1187 | <li>一个通信器定义一个通信域,也就是一组允许相互通信的进程</li> | 1181 | <li>一个通信器定义一个通信域,也就是一组允许相互通信的进程</li> |
@@ -1195,9 +1189,9 @@ int main(int argc,char *argv[]) | |||
1195 | <li>MPI 进程是 MPI 程序中一个独立参与通信的个体</li> | 1189 | <li>MPI 进程是 MPI 程序中一个独立参与通信的个体</li> |
1196 | <li>MPI 进程组事由一些进程构成的有序集合</li> | 1190 | <li>MPI 进程组事由一些进程构成的有序集合</li> |
1197 | <li>进程号是相对于进程组或通信器而言的,同一进程在不同的进程组可以有不同的进程号</li> | 1191 | <li>进程号是相对于进程组或通信器而言的,同一进程在不同的进程组可以有不同的进程号</li> |
1198 | <li>进程号在进程组或通信器被创建时赋予,取值范围为$[0, np-1]$</li> | 1192 | <li>进程号在进程组或通信器被创建时赋予,取值范围为<span class="math inline">\([0,np-1]\)</span></li> |
1199 | </ul> | 1193 | </ul> |
1200 | <pre><code>#include <stdio.h> | 1194 | <pre><code class="language-c">#include <stdio.h> |
1201 | #include <math.h> | 1195 | #include <math.h> |
1202 | #include <mpi.h> | 1196 | #include <mpi.h> |
1203 | int main(int argc,char *argv[]) | 1197 | int main(int argc,char *argv[]) |
@@ -1251,7 +1245,7 @@ int main(int argc,char *argv[]) | |||
1251 | <p>Talking is cheap, show me the code.</p> | 1245 | <p>Talking is cheap, show me the code.</p> |
1252 | <p>Read the fxxking source code!</p> | 1246 | <p>Read the fxxking source code!</p> |
1253 | </blockquote> | 1247 | </blockquote> |
1254 | <pre><code>#include <stdio.h> | 1248 | <pre><code class="language-c">#include <stdio.h> |
1255 | #include <string.h> | 1249 | #include <string.h> |
1256 | #include <mpi.h> | 1250 | #include <mpi.h> |
1257 | 1251 | ||
@@ -1374,7 +1368,7 @@ int main() | |||
1374 | </tbody> | 1368 | </tbody> |
1375 | </table> | 1369 | </table> |
1376 | <p>现在我们已经学会了六个最基本的 MPI 函数:</p> | 1370 | <p>现在我们已经学会了六个最基本的 MPI 函数:</p> |
1377 | <pre><code>int MPI_Init(int *argc,char ***argv); | 1371 | <pre><code class="language-c">int MPI_Init(int *argc,char ***argv); |
1378 | int MPI_Comm_size(MPI_Comm comm,int *size); | 1372 | int MPI_Comm_size(MPI_Comm comm,int *size); |
1379 | int MPI_Comm_rank(MPI_Comm comm,int *rank); | 1373 | int MPI_Comm_rank(MPI_Comm comm,int *rank); |
1380 | int MPI_Send(const void *buf,int count,MPI_Datatype datatype,int dest,int tag,MPI_Comm comm); | 1374 | int MPI_Send(const void *buf,int count,MPI_Datatype datatype,int dest,int tag,MPI_Comm comm); |
@@ -1397,9 +1391,10 @@ int MPI_Finalize();</code></pre> | |||
1397 | <p>非阻塞通信返回就不意味着通信完成。MPI 提供了对非阻塞通信是否完成的检测,主要是<code>MPI_Wait</code>与<code>MPI_Test</code>函数。</p> | 1391 | <p>非阻塞通信返回就不意味着通信完成。MPI 提供了对非阻塞通信是否完成的检测,主要是<code>MPI_Wait</code>与<code>MPI_Test</code>函数。</p> |
1398 | <p>换言之,阻塞通信就是需要等待通讯结束再继续进行,而非阻塞则是<strong>计算与通信时间重叠</strong>,从而提高了系统性能。</p> | 1392 | <p>换言之,阻塞通信就是需要等待通讯结束再继续进行,而非阻塞则是<strong>计算与通信时间重叠</strong>,从而提高了系统性能。</p> |
1399 | <!-- 这幅图记得改,改成非阻塞通信的发送方与接收方的时序图 --> | 1393 | <!-- 这幅图记得改,改成非阻塞通信的发送方与接收方的时序图 --> |
1394 | <img src="https://www.qin-juan-ge-zhu.top/images/courseNotes/parallel_2.png"> | ||
1400 | <h4 id="非阻塞通信">非阻塞通信</h4> | 1395 | <h4 id="非阻塞通信">非阻塞通信</h4> |
1401 | <p>no bb:</p> | 1396 | <p>no bb:</p> |
1402 | <pre><code>/* | 1397 | <pre><code class="language-c">/* |
1403 | * 非阻塞发送 | 1398 | * 非阻塞发送 |
1404 | * buf: 发送缓冲区的地址 | 1399 | * buf: 发送缓冲区的地址 |
1405 | * count: 发送数据的个数 | 1400 | * count: 发送数据的个数 |
@@ -1420,7 +1415,7 @@ int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, | |||
1420 | <li>发送完成:发送缓冲区的数据已送出,缓冲区可以重新使用(并不代表数据已被接收方接受)。数据有可能被缓冲。</li> | 1415 | <li>发送完成:发送缓冲区的数据已送出,缓冲区可以重新使用(并不代表数据已被接收方接受)。数据有可能被缓冲。</li> |
1421 | <li>接收完成:数据已经写入接收缓冲区,可以正常访问与使用</li> | 1416 | <li>接收完成:数据已经写入接收缓冲区,可以正常访问与使用</li> |
1422 | </ul> | 1417 | </ul> |
1423 | <pre><code>// 阻塞型函数,必须等待指定通信请求完成后才能返回和继续执行下一步 | 1418 | <pre><code class="language-c">// 阻塞型函数,必须等待指定通信请求完成后才能返回和继续执行下一步 |
1424 | int MPI_Wait(MPI_Request *request, MPI_Status *status); | 1419 | int MPI_Wait(MPI_Request *request, MPI_Status *status); |
1425 | // 检测指定的通信请求,不论是否完成都立刻返回,若完成则返回flag=true,反之返回false | 1420 | // 检测指定的通信请求,不论是否完成都立刻返回,若完成则返回flag=true,反之返回false |
1426 | int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);</code></pre> | 1421 | int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);</code></pre> |
@@ -1510,19 +1505,14 @@ int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);</code></pre> | |||
1510 | <h3 id="聚合通信">聚合通信</h3> | 1505 | <h3 id="聚合通信">聚合通信</h3> |
1511 | <p>内容和图片都太多,这里写不下。</p> | 1506 | <p>内容和图片都太多,这里写不下。</p> |
1512 | <h3 id="示例程序">示例程序</h3> | 1507 | <h3 id="示例程序">示例程序</h3> |
1513 | <p>我们还是算算可爱的$\pi$罢,这次利用另一个柿子:</p> | 1508 | <p>我们还是算算可爱的<span class="math inline">\(\pi\)</span>罢,这次利用另一个柿子:</p> |
1514 | <!-- <p><br /><span class="math display">$$ | 1509 | <p><span class="math display">\[ |
1515 | \begin{align*} | ||
1516 | \pi = \int_0^1 \frac 4 {1+x^2} \mathrm{d}x | ||
1517 | \end{align*} | ||
1518 | $$</span><br /></p> --> | ||
1519 | <p><br />$$ | ||
1520 | \begin{align*} | 1510 | \begin{align*} |
1521 | \pi = \int_0^1 \frac 4 {1+x^2} \mathrm{d}x | 1511 | \pi = \int_0^1 \frac 4 {1+x^2} \mathrm{d}x |
1522 | \end{align*} | 1512 | \end{align*} |
1523 | $$<br /></p> | 1513 | \]</span></p> |
1524 | <h4 id="串行程序">串行程序</h4> | 1514 | <h4 id="串行程序">串行程序</h4> |
1525 | <pre><code>#include <stdio.h> | 1515 | <pre><code class="language-c">#include <stdio.h> |
1526 | 1516 | ||
1527 | int num_steps=1000; | 1517 | int num_steps=1000; |
1528 | double width; | 1518 | double width; |
@@ -1543,7 +1533,7 @@ int main() | |||
1543 | return 0; | 1533 | return 0; |
1544 | }</code></pre> | 1534 | }</code></pre> |
1545 | <h4 id="并行程序">并行程序</h4> | 1535 | <h4 id="并行程序">并行程序</h4> |
1546 | <pre><code>#include <stdio.h> | 1536 | <pre><code class="language-c">#include <stdio.h> |
1547 | #include <math.h> | 1537 | #include <math.h> |
1548 | #include <mpi.h> | 1538 | #include <mpi.h> |
1549 | 1539 | ||
@@ -1657,7 +1647,7 @@ int main(int argc,char *argv[]) | |||
1657 | <li>将结果从 device 内存复制到 host,释放 device 和 host 上分配的内存</li> | 1647 | <li>将结果从 device 内存复制到 host,释放 device 和 host 上分配的内存</li> |
1658 | </ul> | 1648 | </ul> |
1659 | <p>在编写代码时,需要使用 NVIDIA 的编译器 nvcc。它也可以用于编译没有 device 代码的程序(也就是 1 一般的 C 程序)。</p> | 1649 | <p>在编写代码时,需要使用 NVIDIA 的编译器 nvcc。它也可以用于编译没有 device 代码的程序(也就是 1 一般的 C 程序)。</p> |
1660 | <pre><code>// hello.cu | 1650 | <pre><code class="language-c">// hello.cu |
1661 | #include <stdio.h> | 1651 | #include <stdio.h> |
1662 | 1652 | ||
1663 | __global__ void helloFromGPU(void) | 1653 | __global__ void helloFromGPU(void) |
@@ -1675,12 +1665,10 @@ int main() | |||
1675 | return 0; | 1665 | return 0; |
1676 | }</code></pre> | 1666 | }</code></pre> |
1677 | <p>编译运行:</p> | 1667 | <p>编译运行:</p> |
1678 | <div class="sourceCode" id="cb40"> | 1668 | <pre><code class="language-bash"># nvcc编译为hello |
1679 | <pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb40-1"><a href="#cb40-1" aria-hidden="true"></a><span class="co"># nvcc编译为hello</span></span> | 1669 | nvcc -o hello hello.cu |
1680 | <span id="cb40-2"><a href="#cb40-2" aria-hidden="true"></a><span class="ex">nvcc</span> -o hello hello.cu</span> | 1670 | # 运行 |
1681 | <span id="cb40-3"><a href="#cb40-3" aria-hidden="true"></a><span class="co"># 运行</span></span> | 1671 | ./hello</code></pre> |
1682 | <span id="cb40-4"><a href="#cb40-4" aria-hidden="true"></a><span class="ex">./hello</span></span></code></pre> | ||
1683 | </div> | ||
1684 | <p>在这段示例程序里,我们需要明白:</p> | 1672 | <p>在这段示例程序里,我们需要明白:</p> |
1685 | <ul> | 1673 | <ul> |
1686 | <li>nvcc 将源码分为 host 和 device 两部分,其中 device 部分由 nvcc 编译,host 由标准的主机编译器(如 gcc)编译</li> | 1674 | <li>nvcc 将源码分为 host 和 device 两部分,其中 device 部分由 nvcc 编译,host 由标准的主机编译器(如 gcc)编译</li> |
@@ -1741,7 +1729,7 @@ int main() | |||
1741 | <li>device 指针指向 GPU 内存</li> | 1729 | <li>device 指针指向 GPU 内存</li> |
1742 | </ul> | 1730 | </ul> |
1743 | <p>因而,在处理 device 内存时候,需要调用 CUDA 的内存管理函数:</p> | 1731 | <p>因而,在处理 device 内存时候,需要调用 CUDA 的内存管理函数:</p> |
1744 | <pre><code>/* | 1732 | <pre><code class="language-c">/* |
1745 | * 第一个参数是指向指针的指针,也就是指针地址 | 1733 | * 第一个参数是指向指针的指针,也就是指针地址 |
1746 | * 因为函数返回值是void,需要把申请下来的内存指针写到这个指针地址里 | 1734 | * 因为函数返回值是void,需要把申请下来的内存指针写到这个指针地址里 |
1747 | * 所以只有申请的时候使用二级指针,使用的时候使用一级指针 | 1735 | * 所以只有申请的时候使用二级指针,使用的时候使用一级指针 |
@@ -1760,9 +1748,9 @@ cudaError_t cudaFree(void *devPtr); | |||
1760 | cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, cudaMemcpyKind kind);</code></pre> | 1748 | cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, cudaMemcpyKind kind);</code></pre> |
1761 | <p>需要注意的是,<code>cudaMemcpy</code>函数是同步的,也就是说,只有当数据复制完成后,才会返回。若需要异步,也就是调用函数之后立刻返回而不是等待数据传输完成,则可以使用<code>cudaMemcpyAsync</code>函数: | 1749 | <p>需要注意的是,<code>cudaMemcpy</code>函数是同步的,也就是说,只有当数据复制完成后,才会返回。若需要异步,也就是调用函数之后立刻返回而不是等待数据传输完成,则可以使用<code>cudaMemcpyAsync</code>函数: |
1762 | </p> | 1750 | </p> |
1763 | <pre><code>cudaError_t cudaMemcpyAsync(void *dst, const void *src, size_t count, cudaMemcpyKind kind, cudaStream_t stream = 0);</code></pre> | 1751 | <pre><code class="language-c">cudaError_t cudaMemcpyAsync(void *dst, const void *src, size_t count, cudaMemcpyKind kind, cudaStream_t stream = 0);</code></pre> |
1764 | <p>简单的示例程序:</p> | 1752 | <p>简单的示例程序:</p> |
1765 | <pre><code>#include <stdio.h> | 1753 | <pre><code class="language-c">#include <stdio.h> |
1766 | #include <cuda_runtime.h> | 1754 | #include <cuda_runtime.h> |
1767 | 1755 | ||
1768 | __global__ void add(int *a,int *b,int *c) | 1756 | __global__ void add(int *a,int *b,int *c) |
@@ -1812,7 +1800,7 @@ int main() | |||
1812 | <p>在任何时间,warp 中所有线程必须执行相同的指令,但如果遇到条件控制就会出现问题,这时候可以选择为块中的线程创建一个不同的控制路径,将执行相同分支行为的线程放在同一个 warp | 1800 | <p>在任何时间,warp 中所有线程必须执行相同的指令,但如果遇到条件控制就会出现问题,这时候可以选择为块中的线程创建一个不同的控制路径,将执行相同分支行为的线程放在同一个 warp |
1813 | 中,从而减少分支分歧/提高性能。</p> | 1801 | 中,从而减少分支分歧/提高性能。</p> |
1814 | <h3 id="向量加法">向量加法</h3> | 1802 | <h3 id="向量加法">向量加法</h3> |
1815 | <pre><code>#define N 512 | 1803 | <pre><code class="language-c">#define N 512 |
1816 | 1804 | ||
1817 | __global__ void add(int *a,int *b,int *c) | 1805 | __global__ void add(int *a,int *b,int *c) |
1818 | { | 1806 | { |
@@ -1857,8 +1845,9 @@ int main() | |||
1857 | 1845 | ||
1858 | return 0; | 1846 | return 0; |
1859 | }</code></pre> | 1847 | }</code></pre> |
1860 | <p>CUDA 后续的理论讲解较多,恕不能一一列举于此。直接看 PPT 罢。</p> | 1848 | <p>CUDA后续的理论讲解较多,恕不能一一列举于此。直接看PPT罢。</p> |
1861 | <script src="https://www.qin-juan-ge-zhu.top/common/js/comment4works.js"></script> | 1849 | <p class="time">2023.7.8</p> |
1850 | <script src="https://www.qin-juan-ge-zhu.top/common/js/comment.js"></script> | ||
1862 | </div> | 1851 | </div> |
1863 | </div> | 1852 | </div> |
1864 | </body> | 1853 | </body> |