summaryrefslogtreecommitdiffstats
path: root/courseNotes/parallel.html
diff options
context:
space:
mode:
authorWe-unite <3205135446@qq.com>2025-01-07 12:36:05 +0800
committerWe-unite <3205135446@qq.com>2025-01-07 12:36:05 +0800
commit4d88ef666eee1b6f191f6e85b00acf8d5a2d1899 (patch)
tree68391846bae84f9546b0d089c012afc336a6e6bd /courseNotes/parallel.html
parent11e64c5804b696f170b9d5d881befbabc4a4e85c (diff)
downloadmyweb-new_highlightjs.tar.gz
myweb-new_highlightjs.zip
highlight don't use auto-detect but given languagenew_highlightjs
In this commit, lot's of things is changed. Hope they all runs currectly. Now highlight.js is supporting more and more proguam languages, but the auto detection always go wrong, even for common languages like c, bash, python, makefile. Use Given Language ------------------ As you know, I always write markdown and convert to html by pandoc. In the old, "```cpp" in markdown will be deleted first to keep the embeded code clean and not highlighted, then I can use highlight.js. But this causes that html document doesn't know the language. This time, md2html.sh is changed: pandoc use "--no-highlight" argument to keep code clean, and it will output like this: ```html <pre class="cpp"><code>...</code></pre> ``` Although there may be other tags between `<code></code>`, it's clear that `<pre class="xxx"><code>` is nested tightly, except some space characters or \n. Then, sed deal with the whole doc(not line by line), replace `<pre class="xxx"><code>` with `<pre><code class="language-xxx">`. That's it! Math Formula ------------ Math formular is also a problem during convertion by pandoc. In the old it's dealed menually. Now pandoc use "--mathjax=none", then formula is no longer showed by pandoc, but only `<span class="math xxx">\( formula \)</span>`. And the math tool I used will deal with it. Mermaid picture ---------------- pandoc doesn't support convert mermaid in markdown to html picture. Let's have a warning!
Diffstat (limited to 'courseNotes/parallel.html')
-rw-r--r--courseNotes/parallel.html169
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 &lt;stdio.h&gt; 44 <pre><code class="language-c">#include &lt;stdio.h&gt;
43#include &lt;stdlib.h&gt; 45#include &lt;stdlib.h&gt;
44#include &lt;pthread.h&gt; 46#include &lt;pthread.h&gt;
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 &lt;stdio.h&gt; 118 <pre><code class="language-c">#include &lt;stdio.h&gt;
116#include &lt;stdlib.h&gt; 119#include &lt;stdlib.h&gt;
117#include &lt;pthread.h&gt; 120#include &lt;pthread.h&gt;
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 &lt;stdio.h&gt; 177 <pre><code class="language-c">#include &lt;stdio.h&gt;
175#include &lt;stdlib.h&gt; 178#include &lt;stdlib.h&gt;
176#include &lt;pthread.h&gt; 179#include &lt;pthread.h&gt;
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 &lt;semaphore.h&gt; // 信号量头文件 266 <pre><code class="language-c">#include &lt;semaphore.h&gt; // 信号量头文件
264 267
265// 定义信号量 268// 定义信号量
266sem_t sem; 269sem_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 &lt;stdio.h&gt; 302 <pre><code class="language-c">#include &lt;stdio.h&gt;
300#include &lt;pthread.h&gt; 303#include &lt;pthread.h&gt;
301#include &lt;semaphore.h&gt; 304#include &lt;semaphore.h&gt;
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 &lt;pthread.h&gt; 366 <pre><code class="language-c">#include &lt;pthread.h&gt;
364 367
365// 定义路障 368// 定义路障
366pthread_barrier_t barrier; 369pthread_barrier_t barrier;
@@ -379,7 +382,7 @@ int pthread_barrier_wait(pthread_barrier_t *barrier);
379// 销毁路障 382// 销毁路障
380int pthread_barrier_destroy(pthread_barrier_t *barrier);</code></pre> 383int pthread_barrier_destroy(pthread_barrier_t *barrier);</code></pre>
381 <p>示例程序如下:</p> 384 <p>示例程序如下:</p>
382 <pre><code>#include &lt;stdio.h&gt; 385 <pre><code class="language-c">#include &lt;stdio.h&gt;
383#include &lt;stdlib.h&gt; 386#include &lt;stdlib.h&gt;
384#include &lt;pthread.h&gt; 387#include &lt;pthread.h&gt;
385#include &lt;time.h&gt; 388#include &lt;time.h&gt;
@@ -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 &lt;pthread.h&gt; 433 <pre><code class="language-c">#include &lt;pthread.h&gt;
431 434
432// 定义条件变量 435// 定义条件变量
433pthread_cond_t cond; 436pthread_cond_t cond;
@@ -456,7 +459,7 @@ int pthread_cond_broadcast(pthread_cond_t *cond);
456 */ 459 */
457int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);</code></pre> 460int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);</code></pre>
458 <p>示例程序,这个程序真没看明白:</p> 461 <p>示例程序,这个程序真没看明白:</p>
459 <pre><code>#include &lt;stdio.h&gt; 462 <pre><code class="language-c">#include &lt;stdio.h&gt;
460#include &lt;stdlib.h&gt; 463#include &lt;stdlib.h&gt;
461#include &lt;pthread.h&gt; 464#include &lt;pthread.h&gt;
462#include &lt;unistd.h&gt; 465#include &lt;unistd.h&gt;
@@ -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 &lt;pthread.h&gt; // 哈哈,没想到吧 523 <pre><code class="language-c">#include &lt;pthread.h&gt; // 哈哈,没想到吧
521 524
522// 定义读写锁 525// 定义读写锁
523pthread_rwlock_t rwlock; 526pthread_rwlock_t rwlock;
@@ -541,7 +544,7 @@ int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
541// 销毁 544// 销毁
542int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);</code></pre> 545int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);</code></pre>
543 <p>示例程序:</p> 546 <p>示例程序:</p>
544 <pre><code>#include &lt;stdio.h&gt; 547 <pre><code class="language-c">#include &lt;stdio.h&gt;
545#include &lt;stdlib.h&gt; 548#include &lt;stdlib.h&gt;
546#include &lt;string.h&gt; 549#include &lt;string.h&gt;
547#include &lt;pthread.h&gt; 550#include &lt;pthread.h&gt;
@@ -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 &lt;omp.h&gt; 655#include &lt;omp.h&gt;
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 &lt;stdio.h&gt; 680 <pre><code class="language-c">#include &lt;stdio.h&gt;
680#include &lt;omp.h&gt; 681#include &lt;omp.h&gt;
681 682
682int main() 683int 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&#39;m thread 1 692 <pre><code class="language-plain">Hello World! I&#39;m thread 1
692Hello World! I&#39;m thread 2 693Hello World! I&#39;m thread 2
693Hello World! I&#39;m thread 3 694Hello World! I&#39;m thread 3
694Hello World! I&#39;m thread 5 695Hello World! I&#39;m thread 5
@@ -772,7 +773,7 @@ Hello World! I&#39;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)
776num_threads(scalar-integer-expression) 777num_threads(scalar-integer-expression)
777default(shared|none) 778default(shared|none)
778private(list) 779private(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 &lt;omp.h&gt; 811 <pre><code class="language-c">#include &lt;omp.h&gt;
811#include &lt;stdio.h&gt; 812#include &lt;stdio.h&gt;
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为任务块大小
861schedule(dynamic,chunk_size) // 动态分配,chunk_size为任务块大小,按照先来先服务原则分配 862schedule(dynamic,chunk_size) // 动态分配,chunk_size为任务块大小,按照先来先服务原则分配
862schedule(guided,chunk_size) // 动态分配。任务块大小可变,先大后小,chunk_size为最小任务块大小 863schedule(guided,chunk_size) // 动态分配。任务块大小可变,先大后小,chunk_size为最小任务块大小
863schedule(runtime) // 具体调度方式在运行时才确定,由环境变量`OMP_SCHEDULE`指定</code></pre> 864schedule(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(&quot;Thread %d is here!\n&quot;,tid); 946 printf(&quot;Thread %d is here!\n&quot;,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,...)
975private(varname,...)</code></pre> 976private(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 &lt;stdio.h&gt; 1098 <pre><code class="language-c">#include &lt;stdio.h&gt;
1104#include &lt;omp.h&gt; 1099#include &lt;omp.h&gt;
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 &lt;stdio.h&gt; 1159#include &lt;stdio.h&gt;
1164#include &lt;mpi.h&gt; // MPI的头文件 1160#include &lt;mpi.h&gt; // 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 1172mpicc -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> 1175mpirun -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 &lt;stdio.h&gt; 1194 <pre><code class="language-c">#include &lt;stdio.h&gt;
1201#include &lt;math.h&gt; 1195#include &lt;math.h&gt;
1202#include &lt;mpi.h&gt; 1196#include &lt;mpi.h&gt;
1203int main(int argc,char *argv[]) 1197int 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 &lt;stdio.h&gt; 1248 <pre><code class="language-c">#include &lt;stdio.h&gt;
1255#include &lt;string.h&gt; 1249#include &lt;string.h&gt;
1256#include &lt;mpi.h&gt; 1250#include &lt;mpi.h&gt;
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);
1378int MPI_Comm_size(MPI_Comm comm,int *size); 1372int MPI_Comm_size(MPI_Comm comm,int *size);
1379int MPI_Comm_rank(MPI_Comm comm,int *rank); 1373int MPI_Comm_rank(MPI_Comm comm,int *rank);
1380int MPI_Send(const void *buf,int count,MPI_Datatype datatype,int dest,int tag,MPI_Comm comm); 1374int 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">// 阻塞型函数,必须等待指定通信请求完成后才能返回和继续执行下一步
1424int MPI_Wait(MPI_Request *request, MPI_Status *status); 1419int MPI_Wait(MPI_Request *request, MPI_Status *status);
1425// 检测指定的通信请求,不论是否完成都立刻返回,若完成则返回flag=true,反之返回false 1420// 检测指定的通信请求,不论是否完成都立刻返回,若完成则返回flag=true,反之返回false
1426int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);</code></pre> 1421int 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 &lt;stdio.h&gt; 1515 <pre><code class="language-c">#include &lt;stdio.h&gt;
1526 1516
1527int num_steps=1000; 1517int num_steps=1000;
1528double width; 1518double 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 &lt;stdio.h&gt; 1536 <pre><code class="language-c">#include &lt;stdio.h&gt;
1547#include &lt;math.h&gt; 1537#include &lt;math.h&gt;
1548#include &lt;mpi.h&gt; 1538#include &lt;mpi.h&gt;
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 &lt;stdio.h&gt; 1651#include &lt;stdio.h&gt;
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> 1669nvcc -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);
1760cudaError_t cudaMemcpy(void *dst, const void *src, size_t count, cudaMemcpyKind kind);</code></pre> 1748cudaError_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 &lt;stdio.h&gt; 1753 <pre><code class="language-c">#include &lt;stdio.h&gt;
1766#include &lt;cuda_runtime.h&gt; 1754#include &lt;cuda_runtime.h&gt;
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>