練習問題・課題一覧
コア数 | 周波数 | |
ログインノード | 28 | 2.7GHz |
計算ノード | 36 | 2.1GHz |
結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他 |
firstnet-asone-1-1550-04-23244-cwQ |
11,646円 19,410円 |
【AS ONE】汎用器具·消耗品|金属、ホーロー容器·バット類|金属、樹脂バット類|特大
■商品番号·規格:特大:1個[1-1550-04]
※取り寄せ品の納期については、メーカー在庫有時の表記となっております。
商品欠品等により、通常よりお時間がかかる場合がございます。予めご了承ください。
ログインノード上, それもホームディレクトリ直下ではなく, lustreのディレクトリに移動して, ファイル一式をダウンロードせよ (lustreのディレクトリに移動しないとジョブを投げるときにエラーになる). 手順は以下(t03001 の部分は適宜自分のアカウント名に置き換える).
mac端末$ ssh t03001@reedbush-u1.cc.u-tokyo.ac.jp reedbush-u1:$ cdw (または cd /lustre/gt03/t03001) reedbush-u1:$ git clone https://tau@gitlab.eidos.ic.i.u-tokyo.ac.jp/tau/cs-gairon-ex.git最初のsshの設定ができていない人は, 前回の 松本先生の演習スライド (ITC-LMS 計算科学概論0416.pdf)を参照.
makeコマンドを実行すればコンパイルできるようになっているが, コンパイルオプションを変えたいときなどは必要に応じて Makefileは自分で変更すること. もちろんコマンドを自分で入力しても良い
$ cp ../91submit/submit.sh # どうせ多少いじるので手元にコピーがお薦め $ ./submit.shとすると, 勝手にqsubをして, コマンドを実行してくれるようになっています (デフォルトは perf stat hostname を実行するけど深い意味はない). というエラーが出たらそれは多分, 授業時間外に授業時間用のキュー (q-lecture4) に投げようとしたからです. 授業時間外は, ファイル先頭の を に変更して下さい. その他やりたいことに合わせてファイル先頭のパラメータと, batch_main()という関数の中身のコマンドを書き換えて下さい. デフォルトの設定は:
reedbush-u1$ コマンド ...または
$ コマンド ....のように表記する. いずれの場合も $ マーク以前は コマンドの入力待ちであることを示す印(プロンプト)であり, 自分で入力するのは これ以降の太字, 下線部分である.
演習のめあて: SIMD化がうまく行っているかの確認, 意図した高速化が行われていない場合の追求手段として, コンパイラが出力した実際に出ているアセンブリを見られるようになる.
一つ例で説明する. 以下のファイルを作り(名前はなんでもよいが, plus.cとする),
以下でコンパイルするreedbush-u1$ gcc -O3 -march=native -S plus.cと, plus.s というファイルができる.
vaddss %xmm1, %xmm0, %xmm0
retはリターン命令でこれで関数の実行が終了する. つまりこの関数は 実質的には一命令に翻訳されている.
XXXps | packed single precision | 単精度用ベクトル命令 |
XXXpd | packed double precision | 倍精度用ベクトル命令 |
XXXss | scalar single precision | 単精度用スカラ命令 |
XXXsd | scalar double precision | 倍精度用スカラ命令 |
ループを含む文の結果についても一つ見ておく.
float loop(float x, float a, float b, long n) { for (long i = 0; i < n; i++) { x = a * x + b; } return x; }コンパイルする時にこんなエラーが出たら,
gcc -march=native loop.c -S -O3 loop.c: In function ‘loop’: loop.c:: error: ‘for’ loop initial declarations are only allowed in C99 mode for (long i = 0; i < n; i++) { ^ loop.c:: note: use option -std=c99 or -std=gnu99 to compile your code-std=c99または-std=gnu99というオプションをつけてコンパイルしてあげて下さい. 翻訳結果は,
.file "loop.c" .text .p2align 4,,15 .globl loop .type loop, @function loop: .LFB0: .cfi_startproc testq %rdi, %rdi jle .L6 xorl %eax, %eax .p2align 4,,10 .p2align 3 .L3: vmulss %xmm0, %xmm1, %xmm0 addq $1, %rax cmpq %rax, %rdi vaddss %xmm2, %xmm0, %xmm0 jne .L3 .L6: rep ret .cfi_endproc .LFE0: .size loop, .-loop .ident "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406" .section .note.GNU-stack,"",@progbits
最後に有用なトリックを一つ. 少し大きなコードになるとコンパイラの出力は すぐに大きく複雑になって, 関係するところがわからなくなるので, 注目したいところを
float loop(float x, float a, float b, long n) { asm volatile("# =========");倉茂電工 ビニソフト VCTF22 100M VCTF22_6X0.5SQ-100 期間限定 ポイント10倍asm volatile("# ........."); return x; }のように囲んでやる. asm volatile(...) に書いた中身(...) は, コンパイラは何のことか知らずに,アセンブリコードにそのまま挿入される (本当はC言語で書けない命令を自分で埋め込むための機能だがここではそれを 目的外使用している). 出てきたアセンブリファイル(.s)内で, =====や.....を検索すれば, そこで囲まれた部分が (おそらく)ループ本体を実行している部分である.
なおこれをコンパイルする際に
$ gcc -std=c99 -march=native loop.c -S -O3 loop.c: In function ‘loop’: loop.c:: error: ‘asm’ undeclared (first use in this function) asm volatile("# ======="); ^
富士元 ハンチャンマン専用チップ 超硬M種 ZA20D SDMW11T4AFEN12 ZA20D【10個】-std=gnu99をつけてコンパイルしてあげてください. (asm volatileはGNU Cコンパイラの拡張機能で, デフォルトはオンなのだが富士元 ぴんこ 刃径φ2 AlCrNコーティング PKP0245C≪お取扱終了予定商品≫を付けると抑止されてしまう.
reedbush-u1$ makeコマンドを実行するだけで行える
演習のめあて: プログラムの性能の良し悪しは最終的には実行時間に現れるものだが, 漠然と時間だけをはかって速くなった, 遅くなった, という情報を得るだけでは, そのプログラムが「意図した性能を出しているのか」 「マシンの限界に近い性能を出しているのか」 などは把握しづらい. それをしっかりと把握するにはまず「クロック数(サイクル数)」で測るのがよい. 1クロックに実行できた浮動小数点演算数(flops/clock)や, ループを一回(1 iteration)回るのにかかったクロック数, などを測れるようになるとよい.
CPUには, クロック数を取得する命令がある.
#include <x86intrin.h>を挿入すると_rdtsc() という関数が使えるようになる. この関数は現在の時刻をクロック数で返す.
従って以下のようにして, プログラム中の 2点間の実行に要したクロック数を計測できる. _rdtsc() の呼び出し自身にもある程度時間かかるので(これ自体一度測ってみよう, というのは優れた姿勢である), あまりにも小さな計算を正確に測ることはできない.
long long ts0 = _rdtsc(); /* 計測したい部分始め */ ... /* 計測したい部分終わり */ long long ts1 = _rdtsc(); long long dt = ts1 - ts0; /* dt が要したクロック数 */
クロック数を測定する際にひとつ注意しなくてはならないことがある. それは, プロセッサの周波数は, 消費電力の削減のために, 負荷に応じて自動的に調節されているということである. 大雑把に言えば, 負荷が小さければプロセッサの動作周波数は小さくなり, 計算中心の負荷を与えると向上する. つまり, プロセッサが1クロックを刻む時間というのは, 負荷に応じて 変動しているのである(動的電圧・周波数調整; Dynamic Voltage and Frequency Scaling). 特に, プロセッサの通常の周波数を超えて動作することもあり, Intelの商標ではターボブーストと呼ばれている.
実は, 最近のIntelプロセッサで, _rdtsc()によって返されるクロック数というのは, referenceクロックと呼ばれ, プロセッサの動作周波数が変わっても, 同じペースで刻まれる. 言い換えれば, 正確に実時間に比例して常に一定ペースで刻まれる.
一方, プロセッサの限界性能が出ているかどうか, などの測定時には, プロセッサのクロック数を測るほうが分かりやすい. というのも「このCPUの限界は, 1 クロックに2つのfmadd命令(*)」 などというときの「クロック」はプロセッサのクロック数のことだからである. 実際にどんな周波数で動作していようと(*)は常に事実である.
プロセッサのクロック数を得るための, _rdtsc()と同じくらいお手軽 で標準的な(どの環境でも使える)方法は自分の知る限り存在しない. そこでそれに相当するものを演習用に作って提供している.
#include <rdcyc.h>として,
long long t0 = _rdcyc(); 「測定したい区間」 long long t1 = _rdcyc(); long long dt = t1 - t0;のようにして, 「測定したい区間」にかかった時間を, プロセッサの クロックで返してくれる. これは, 普通の環境で標準的に使える 関数ではないので注意(_rdtsc()はGCCが使える環境では 常に使える).
注意として, このクロック数はCPUのコア毎 にどれだけずれているかわからない. 従って_rdcyc()を2度呼び出して 引き算をするときの2度の呼び出しは, 同じスレッドによって呼び出されなくては ならない. 後にOpenMPを用いたマルチコアプログラムを測定するときに 注意が必要である.
perfというコマンドを使うと, プログラム全体のクロック数や命令数など様々なものが いとも簡単に計測できる.
perf stat コマンドとして, 「コマンド」を実行すると, そのプログラムが開始から終了までに要した クロック数や, 実行した命令数が簡単に取得できる. 例: perfコマンドは, プログラム実行中の一部分だけを測定することができない. したがってプログラム中で, SIMD化されている部分の性能だけを測ったり, 並列化されている部分の性能だけを測るなど, 一部分だけを精密に測ることはできない. しかし, 手軽でありながら, 上記に表示されているとおり, 命令数, 分岐命令の数や 分岐予測ミスなど, 有用な情報を多数表示してくれる, という利点がある. 実は計測できるのは上記で表示されている指標にとどまらない. キャッシュミスなど, プログラムの性能解析に必須のその他の情報も表示してくれる. perf listで, 測定できる指標の一覧を 取得でき, perf stat -e 指標名 -e 指標名 ... コマンドで, 測定する指標を指定できる.
perfコマンドは, referenceクロック数と, プロセッサのクロック数の両方を 計測, 表示できる. 前者はperf statに -e ref-cycles, 後者は-e cyclesという イベント名を指定することで取得できる.
いきなり問題に入る前に, 簡単な例題を通してSIMD化, 最大性能を得るところまでを説明する. 説明は長くなるので, 自分には不要だと思う人は飛ばしても良い. 以下のプログラムは漸化式:
$$ x_{j} = a x_{j - 1} + b $$に沿った計算を$n$回繰り返す(つまり$x_n$を求める)もので, それを $m$ 個の異なる初期値(配列Xに格納されている) に対してすべて行うものである (ちなみに$a < 1$であれば, どんな初期値から始めても結局 $\frac{b}{1-a}$に収束する). 当然のことながら, 異なる$i$ (X[0], X[1], ...)に対する計算はすべて独立に 実行できるので, SIMD化も並列化も容易である.
void linear_recurrence(float a, float b, float * X, long m, long n) { for (long i = 0; i < m; i++) { float x = X[i]; for (long j = 0; j < n; j++) { x = a * x + b; /* 漸化式の計算 */ } X[i] = x; } }この説明用例題のコードは 01lrec 以下にある.
このプログラムをm = 20160, n = 266305アサヒペン シルバーコート シルバー/10L
reedbush-u1$ make gcc -march=native -Wall -Wextra -std=gnu99 -O3 -I/lustre/pz0092/z30092/cs-gair\ on-ex/90rdcyc lrec.c -o lrec -L/lustre/pz0092/z30092/cs-gairon-ex/90rdcyc -Wl,-\ R/lustre/pz0092/z30092/cs-gairon-ex/90rdcyc -lrdcyc gcc -march=native -Wall -Wextra -std=gnu99 -O3 -I/lustre/pz0092/z30092/cs-gair\ on-ex/90rdcyc lrec.c -S $ ./lrec OK m = 20160, n = 266305 5368708800 iterations 26850156667 clocks 0.399901 flops/clock 5.001232 clocks/iter5368708800 iterationsは繰り返しの全回数で, このプログラムにおいては m * n のことである. _rdcyc()によって測定したクロック数が 26850156667 である.
1コアあたりの最大性能は vfmaddps を毎クロック2個 (乗算16と加算16) 実行できる というものであった.
このプログラムは最内ループで乗算1回と加算1回を行うので, うまくすると1コアで, 32 flops/clockの性能が出る望みがある. それと比べると, ここで観測された性能はその約1/80程度である. そのgapの多くの部分は SIMD命令を使っていないことによるものであろう. だがそれによって8倍高速化したとしても まだgapはありそうである. その話は一旦後回しにしてまずはSIMD化をする.
先へ進む前にこのコードがどういう命令列になっているかは一度見ておく必要がある. 例えば, このコードがコンパイラによってSIMD化されていたら, それをあえて手動でSIMD化するなどというのは見当違いということになる. まず以下のように最内ループの前後に目印を置き,
void linear_recurrence(float a, float b, float * X, long m, long n) { for (long i = 0; i < m; i++) { float x = X[i]; asm volatile("# =========== "); for (long j = 0; j < n; j++) { x = a * x + b; /* 漸化式の計算 */ } asm volatile("# ----------- "); X[i] = x; } }
$ gcc -o lrec.s -O3 -march=native -std=gnu99 -S lrec.cとしてコンパイルし, lrec.s をエディタで開き, 目印を検索すると以下が見つかる. なお, -march=nativeは Broadwell (ログインノード)で使える命令セットを全て使う (つまりはAVX2を使う)という意味である. Reedbushにおいてはログインノードと 計算ノードのマイクロアーキテクチャが同じなのでこれでよい.
これ以前省略 # =========== # 0 "" 2 #NO_APP testq %rdx, %rdx jle .L3 xorl %eax, %eax .p2align 4,,10 .p2align 3 .L4: addq $1, %rax vfmadd132ss %xmm0, %xmm1, %xmm2 cmpq %rdx, %rax jne .L4 .L3: #APP # 23 "lrec.c" 1 # ----------- これ以降省略.jne .L4 というジャンプ命令と, .L4: というラベルの位置から, 以下の3命令が繰り返し実行されているとわかる.
addq $1, %rax vfmadd132ss %xmm0, %xmm1, %xmm2 cmpq %rax, %rdx三菱 MVE WSTAR汎用 超硬ソリッドドリル3D 外部給油形6.2mm DP1020 MVE0620X03S070_DP1020-DP1020 期間限定 ポイント10倍
SIMD化の方針は当然ながら, 連続した8つのiに対する計算をSIMD命令でまとめて 行うというものである. 擬似的に書けば,
void linear_recurrence(float a, float b, float * X, long m, long n) { for (long i = 0; i < m; i += 8) { floatv x = X[i:i+8]; for (long j = 0; j < n; j++) { x = a * x + b; /* 漸化式の計算 */ } X[i:i+8] = x; } }というもの. X[i:i+8] はX[i], ..., X[i+7]を意味する, ここだけの記法. 実際のコードでは 授業でも説明した _mm256_loadu_ps, _mm256_storeu_ps という intrinsic関数を使って要素の取り出し, 書き込みを行う. また, a * x + b の部分が, 「スカラー * ベクタ + スカラ」という形をしているので, a, bをベクトル型に拡張 (それぞれ{a,a,a,a,a,a,a,a}, {b,b,b,b,b,b,b,b}に)しておく. それには _mm256_set1_ps というintrinsic関数を使う. なお, 新しいGCCではこの 拡張を勝手に行ってくれるので必要ない.
実際のコードは以下.
typedef float floatv __attribute__((vector_size(32))); enum { n_lanes = sizeof(floatv) / sizeof(float) }; /* = 8 */ void linear_recurrence(float a, float b, float * X, long m, long n) { floatv a_ = _mm256_set1_ps(a); floatv b_ = _mm256_set1_ps(b); for (long i = 0; i < m; i += n_lanes) { floatv x = _mm256_loadu_ps(&X[i]); for (long j = 0; j < n; j++) { x = a_ * x + b_; /* 漸化式の計算 */ } _mm256_storeu_ps(&X[i], x); } }
測定結果は,
$ ./lrec OK m = 20160, n = 266305 671088600 iterations 3358011309 clocks 3.197553 flops/clock 5.003827 clocks/iter割とあっさりと約8倍高速化していることがわかる(そのために作った例題なので...). なお, ここでの671088600 iterationsは, ((m / 8) * n)のことである. 8つのX[i]を一回で処理しているので繰り返しの数は1/8になる.
もちろんここでも生成された命令列を見てみること. もちろんのこと, 以下の通りvfmaddps 命令が使われていることが確認できる.
# =========== # 0 "" 2 #NO_APP testq %rdx, %rdx jle .L12 xorl %eax, %eax .p2align 4,,10 .p2align 3 .L13: addq $1, %rax vfmadd132ps %ymm2, %ymm1, %ymm0 cmpq %rdx, %rax jne .L13 .L12: #APP # 40 "lrec.c" 1 # -----------
さてここで得られた 3.197553 flops/clock というのは 1コアで期待できる最大性能(32 flops/clock)と比べてまだ開き がある(最大性能のほぼ1/10). それが何かについて説明する. 授業で, Broadwell CPU は vfmaddps 命令を1クロックに2個実行できると述べた. ところが今現実に目の当たりにしている現象は, 以下のループ:
.L15: addq $1, %rax vfmadd132ps %ymm2, %ymm1, %ymm0 cmpq %rax, %rdx jne .L15を一回りするのに 5.003827 クロックかかるということである. 1クロックにつき2個実行したいと期待したvfmadd132ps命令が, 実際には5.003827クロックに1個しか実行できていないということである.
その理由は, 計算の「遅延」およびデータの依存関係にある. 実は
vfmadd132ps %ymm2, %ymm1, %ymm0 (ymm0 = ymm2 * ymm0 + ymm1)という命令は, Broadwell CPU上では, 計算が始まってからその結果が実際に出るまでの間に5クロック かかる. これをその命令の「遅延(latency)」という. なお, 5クロックの遅延がある という事実は, 授業でも紹介した Intel アズワン(AS ONE) ステンレス作業台(引出し付) 1200×600×800mm A-1200 1台[個人宅配送不可]を見て, _mm256_fmadd_ps の説明を読むと確認できる.
さてここで, vfmaddpsに「5クロックかかる」という話と, vfmadd132psを「1クロックに2個実行できる」という話を聞いて, どちらが本当なのだと頭が混乱する人もいるかも知れない. どちらも本当である. 分かりやすい例え話で説明する.
今, 飛行機1便で100人の人が運べるとして, 飛行機で, 1時間あたりになるべくたくさんの人を東京から九州に運びたいとしよう. vfmaddpsの「遅延がに5クロック」であるというのは, 一機の飛行機で東京から九州まで行くのに往復5時間かかる, というのと同じである.
では飛行機で運べるのは「1時間あたり20人 (100/5)」 が限界なのかと言うとそんなことはない. 飛行機の台数(便数)を増やせばいいのである. 一つの飛行機の所要(遅延)時間は同じでも, 便数を増やせば時間あたりに運べる人の総数(スループット)は増える. 「vfmadd132psを1クロックに2個実行できる」というのは, 1時間に2便まで飛行機を 発着できる, というのと同じである. 現実の飛行機の場合, この限界は滑走路の数や安全上の理由で必要な「間隔」によって決まることになる. 1時間2便飛ばすとなると, 東京と九州の間に飛行機が常時10台飛んでいることになる(パイプライン).
プロセッサにおいても同じで, 一つの演算の答えが出るのには5クロックかかる. 一方で多数の演算を並行して実行することはでき, 演算器は毎クロック, 別の演算を受け付けることができる. 演算器の中ではそれらの計算が少しずつずれて実行されている.
話を元に戻すと, つまり上記を繰り返す一連の命令列:
ymm0 = ymm2 * ymm0 + ymm1 ymm0 = ymm2 * ymm0 + ymm1 ymm0 = ymm2 * ymm0 + ymm1 ymm0 = ymm2 * ymm0 + ymm1 ...という命令列では, ymm0に格納される計算結果を次の 命令が使っているため, 5クロックにつき一つの速度でしか進行できない. 例えるならば, いくら飛行機の便数を増やしても, 一人の人が東京-九州間を 10往復するには最低でも 10 x 5 時間かかる, ということである. それが上記の繰り返しに 5.003827 クロックかかっていた理由である.
ここまででやっと, なぜSIMD化しただけのループだと, 1 iterationあたり 5クロックかかってしまうかが理解できた. ではこれ以上速くするにはどうしたらよいか? vfmaddの結果が出るまでの時間 (遅延)がこれ以上縮まることは(CPUを変えない限り)期待できない. 飛行機の速度を速くして, 東京-九州を2時間で往復できるようにすることはできない. できることは飛行機の便数を増やす --- 多数の要素に対する計算を並行して行う --- ことである. 「並行して」といっても, それはマルチコアを使った並列化のことではなく, あくまで同じコアの中で, 依存関係のない計算をオーバーラップさせて 行うということである. 例えば以下は, X[i:i+8], X[i+8:i+16], X[i+16:i+24], X[i+24:i+32] に対する更新を並行して行っている.
命令列を鑑賞すると以下の通りで, 1 iteration内に4つのvfmaddps 命令が生成されている.
実行結果:
flops/clock の値はほぼ4倍 (3.2 x 4 = 12.8) になっている. 同じこととして, 1 iterationにかかるクロック数は5のままである. 1 iterationの計算量を4倍に増やしているにもかかわらず, である. 先ほどのケースと, 今回のケースで, プロセッサ内でおきていることの違いを 図に示すと以下のようになる.クロック | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
x | 0 | 1 | 2 |
クロック | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
x0 | 0 | 1 | 2 | ||||||||||||
x1 | 0 | 1 | 2 | ||||||||||||
x2 | 0 | 1 | 2 | ||||||||||||
x3 | 0 | サンドビック T-MAXPチップ 112 4315 WNMG_08_04_12-WM_4315-4315 10個入 期間限定 ポイント10倍 | 2 |
そのことは変数がいくつあろうと同じなのだが, 異なる変数に対する計算の間には, 依存関係がないため, 複数をオーバーラップさせることができる. vfmaddの演算器が2つあるため, 同じクロックに二個まで発効することができる.
では最大性能を出すには? もうわかったと思うが変数を10個にしてやれば, すべてのクロックで2つのvfmaddが発行される. 10個のvfmaddを発行するのに 5クロックかかるがそのころには(5クロック経過しているので)先に出した結果が 返ってきて, 次のiterationへと(隙間なく)実行が続いて行く.
クロック | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
x0 | 0 | 1 | 2 | ||||||||||||
x1 | 0 | 1 | 2 | ||||||||||||
x2 | 0 | 1 | 2 | ||||||||||||
x3 | 0 | 1 | 2 | ||||||||||||
x4 | 0 | 1 | 2 | ||||||||||||
x5 | 0 | 1 | 2 | ||||||||||||
x6 | 0 | 1 | 2 | ||||||||||||
x7 | 0 | 1 | 2 | ||||||||||||
x8 | 0 | 1 | 2 | ||||||||||||
x9 | 0 | 1 | 2 |
それをやるのに上記でしたように10個の変数を用意してもよいが, ありがたいことに配列を使ってやってもよい.
一般的に言って配列参照 x[k] はload/store命令に翻訳されるのが 普通なので,x[k] = a_ * x[k] + b_; /* 漸化式の計算 */のような計算は load命令; vfmadd命令; store命令, という命令列になってもおかしくはなく, そうなってしまうと(load/store命令の遅延が重なる, store命令が1クロックに1つまでしか 実行できない, という二つの理由により)最大性能は出なくなるのだが, コンパイラが最適化をして x[0], x[1], ..., x[9] それぞれを(メモリに置かず)レジスタで保持してくれるおかげで, これで最大性能が出る. もちろん, これで最大性能が出るということを確認するには, 実際に生成された命令列を見る以外にはない. 以下が生成されたコードで, ループの中 (.L30: 〜 jne .L30まで) が, メモリアクセスのない, vfmadd132ps 命令10個に変換されていることがわかる. このループが, 5クロックに一回のペースで回れば, 毎クロック2つのvfmadd132ps 命令が実行できている --- すなわち最大性能 --- ということになる.
実際にやってみるとその通りになる.
上記で学んだことを活かして,
なお, nbodyの問題においては, ベクトル化をする際に, 隣り合う粒子の座標(たとえばx座標)が, 連続したアドレスにオカれていないということが問題となる. これを解消するために, データを並べ替える(8粒子のx座標が連続して並んだようなデータにする)必要がある.
$$ \int_{[-2,2] \times [-2,2]} f(x,y) dx dy $$を計算するものである. これをOpenMPで並列化するのは簡単で, とするだけでよい. 授業で述べたとおり, が, 複数のスレッドを作りそれらが皆, Sを実行するという構文. は, for文のiterationをスレッド間で分け合って実行する構文である. は両者を合体させたもので, と同じ意味. reduction(+:S) が意味するところを説明する. まず, reduction(+:S) なし(以下)で実行すると, 答えが正しく求まらない. その理由は, 複数のスレッドが, を実行する際に, 複数のスレッドがSを更新するため, 競合状態(授業スライド参照)がおき, Sの更新が正しく行われなくなるからである. reduction(+:S)は,
上記の課題1, 2および余力に応じてオプション課題などをやって, 行列積, N体問題それぞれをどう高速化したか, どのくらい高速化出来たかをまとめて下さい. 単に「こうやったらこれだけ速くなった」ではなく, 「こうしたときの期待される性能はこう(例: このループ一回何クロック) である, やってみたらこういう結果になった (ちゃんと一致した)」ということを書いて下さい. もちろん期待される性能が出ないこともあるとおもいますが, ベストを尽くして下さい.