計算科学概論 --- 演習編
田浦健次朗

(the page is encoded in UTF-8)
アズワン(AS ONE) T字型中心温度計(本体+中心温度計センサー100mm) 105(本体+中心用温度センサー) 1台

目標

Aria A-100C セダートップ オール単板モデル アリア クラシックギターソフトケース付き

目次

練習問題・課題一覧

H700―1X235―32 ポップアップ排水栓上部

環境など

い草 キッチンマット/台所マット 〔イエロー 約60×240cm〕 長方形 日本製 防滑 抗菌 防臭加工 弾力性 『チェック』〔代引不可〕

基本情報

時間内の練習問題提出(進捗報告)環境

こちらの オンリーワンクラブ 横水栓ラモ メッキ HV3-RMY-Mから自分の名前を見つけ, その隣りにあるリンクをクリックして, E列以降に進捗を記入して下さい (ある課題ができたら, セルに1を記入). 授業時間内の進捗把握・フォロー説明の参考とするためでありこれを 使って評価をするわけではありません.

まずこれをやって下さい: 教材ダウンロード

結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他
結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他
firstnet-asone-1-1550-04-23244-cwQ
11,646円 19,410円
パンチングバット, 特大 ONE) 1個 アズワン(AS パンチングバット, アズワン(AS 1個 パンチングバット 特大 ONE), ONE) 特大 パンチングバット 1個 アズワン(AS, 1個 ONE) パンチングバット アズワン(AS 特大, シューズ一覧 【AXEL】 アズワン, 安全靴・作業靴一覧 【AXEL】 アズワン

【AS ONE】汎用器具·消耗品|金属、ホーロー容器·バット類|金属、樹脂バット類|特大
■商品番号·規格:特大:1個[1-1550-04]

※取り寄せ品の納期については、メーカー在庫有時の表記となっております。
商品欠品等により、通常よりお時間がかかる場合がございます。予めご了承ください。

アズワン AS ONE 研究用総合機器2019·サンクアスト2019掲載商品

汎用器具·消耗品>金属、ホーロー容器·バット類>J4金属、樹脂バット類


製品仕様
  • ●材質:ステンレス(SUS304)·電解研磨仕上げ
  • ●穴径:φ6mm
  • ●穴ピッチ:10mm
  • ●型番:特大
  • ●内寸法(mm):350×180×100
  • ●取手:有

パンチングバット
特大 ONE) 1個 アズワン(AS パンチングバット
アズワン(AS 1個 パンチングバット 特大 ONE)
ONE) 特大 パンチングバット 1個 アズワン(AS
1個 ONE) パンチングバット アズワン(AS 特大
シューズ一覧 【AXEL】 アズワン
安全靴・作業靴一覧 【AXEL】 アズワン

パンチングバット
楽天市場】アズワン(AS ONE) ダブルクリップ(Scel-bo) 超特大 クリ
ONE) アズワン(AS 1個 特大 パンチングバット
特大 ONE) 1個 アズワン(AS パンチングバット
パンチングバット 特大 アズワン(AS 1個 ONE)
特大 パンチングバット 1個 アズワン(AS ONE)
アズワン(AS 1個 パンチングバット 特大 ONE)
アズワン(AS ONE) 特大 1個 パンチングバット
パンチングバット 特大 アズワン(AS ONE) 1個
ONE) 特大 パンチングバット 1個 アズワン(AS
パンチングバット 特大 1個 アズワン(AS ONE)
特大 パンチングバット アズワン(AS 1個 ONE)
1個 ONE) パンチングバット アズワン(AS 特大
シューズ一覧 【AXEL】 アズワン
安全靴・作業靴一覧 【AXEL】 アズワン
シューズ一覧 【AXEL】 アズワン
安全靴・作業靴一覧 【AXEL】 アズワン
シューズ一覧 【AXEL】 アズワン
安全靴・作業靴一覧 【AXEL】 アズワン
ONE) パンチングバット 1個 特大 アズワン(AS

ログインノード上, それもホームディレクトリ直下ではなく, 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)を参照.

結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他

【代引不可】【個人宅配送不可】河村(カワムラ) 電灯分電盤 ES ES 1542NK[KWD20685]

結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他

アズワン(AS ONE) デスク スタンダードタイプ 1100×700×720mm SD-ISN117CLSPAWN 1台[個人宅配送不可][送料別途お見積り]
Panasonic ワイヤレスサービスコール壁掛型発信器 ECE3323

コンパイラが生成したアセンブリ言語を覗く

新品未使用品☆アルキュート ブルー シルバーカー 介護 福祉 歩行補助 手押し車

演習のめあて: SIMD化がうまく行っているかの確認, 意図した高速化が行われていない場合の追求手段として, コンパイラが出力した実際に出ているアセンブリを見られるようになる.

最初の例題

一つ例で説明する. 以下のファイルを作り(名前はなんでもよいが, plus.cとする),

以下でコンパイルする
reedbush-u1$ gcc -O3 -march=native -S plus.c
と, plus.s というファイルができる. plus.s をエディタで鑑賞する. 最初の例なので逐一説明するが, 細かいところを気にしなくて良い. 必要なのは, 以降の例題で肝心なところがどういう命令列になっているかを読解できるようになること.
  1. plus:の行が関数plusをコンパイルした結果の開始点. plus関数が呼び出されたときにここから実行が始まる
  2. . (ドット) で始まる行(.cfi_endprocなど)は, 命令ではないのであまり気にせず, 読み飛ばして良い.
  3. 最初の命令は
          vaddss	%xmm1, %xmm0, %xmm0
    
    • vaddss が命令の名前.
    • %xmm1, %xmm0はレジスタ(プログラミング言語の変数みたいなもの)の名前.
    命令の名前(vaddss)から, おそらく足し算命令. まさしく x + y の計算をやっているのだろうと想像できる.
  4. 次の命令
          ret
    
    はリターン命令でこれで関数の実行が終了する. つまりこの関数は 実質的には一命令に翻訳されている.

アセンブリについて最低限の知識

例題2 (ループ)

ループを含む文の結果についても一つ見ておく.

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
【P】【代引不可】【個人宅配送不可】未来工業 VE-28J2 20本 硬質ビニル電線管 [MR15755-20]

調査のための有用なトリック

最後に有用なトリックを一つ. 少し大きなコードになるとコンパイラの出力は すぐに大きく複雑になって, 関係するところがわからなくなるので, 注目したいところを

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≪お取扱終了予定商品≫を付けると抑止されてしまう.

アズワン(AS ONE) 診察台[3段調節式] 750×1800mm 淡ピンク 1台

練習問題1

パナソニック 換気扇 FY-25VF5 換気扇25cm給排 一般換気扇 Panasonic
  1. 00ex/practice.c をコンパイルし, 出力されたアセンブリコードを見て, 以下の問いに答えよ.
    1. load関数で, 式a[0]に対して生成されているのは, どの命令か?
    2. store関数で, 式a[0]=xに対して生成されているのは, どの命令か?
    3. ab_plus_c関数で, p->bに対応して 生成されているのはどの命令か?
    4. sum_b関数は p[0].b + p[1].b + ... + p[n-1].bを計算する. コードを見て, 隣り合う要素のb (例えば p[3].b と p[4].b) が何バイト離れているかを判断せよ.
  2. 02mm/mm.ccの最内ループ(for (k = ...) s += A(i,k) * B(k,j)) に対してどのような命令列が実行されているか調べよ. これはC++のコードで, 出力ファイル(アセンブリコード)における関数名は, 元々の関数名からかなり複雑に変換される. どの部分が上記ループに 対応しているかを調べるために上記の トリックを使うと良い.
  3. 03nbody/nbody.ccの最内ループ一回の計算 (1粒子対の力の計算) に対してどのような命令列が実行されているか調べよ.
Alexander McQueen アレキサンダーマックイーン サンダル White

時間を測定する

【ダンヒル】ダンヒル ビジネスバッグ メンズ DUNHILL L3N781B WINDSOR SINGLE ZIP BRIEFCAS ブリーフケース

演習のめあて: プログラムの性能の良し悪しは最終的には実行時間に現れるものだが, 漠然と時間だけをはかって速くなった, 遅くなった, という情報を得るだけでは, そのプログラムが「意図した性能を出しているのか」 「マシンの限界に近い性能を出しているのか」 などは把握しづらい. それをしっかりと把握するにはまず「クロック数(サイクル数)」で測るのがよい. 1クロックに実行できた浮動小数点演算数(flops/clock)や, ループを一回(1 iteration)回るのにかかったクロック数, などを測れるようになるとよい.

_rdtsc() 関数

CPUには, クロック数を取得する命令がある.

#include <x86intrin.h>
を挿入すると_rdtsc() という関数が使えるようになる. この関数は現在の時刻をクロック数で返す.

従って以下のようにして, プログラム中の 2点間の実行に要したクロック数を計測できる. _rdtsc() の呼び出し自身にもある程度時間かかるので(これ自体一度測ってみよう, というのは優れた姿勢である), あまりにも小さな計算を正確に測ることはできない.

long long ts0 = _rdtsc();
/* 計測したい部分始め */
   ... 
/* 計測したい部分終わり */
long long ts1 = _rdtsc();
long long dt = ts1 - ts0; /* dt が要したクロック数 */
安心の国内発送【GUCCI】ハート シルバーチェーンネックレス

referenceクロックと, プロセッサのクロック

クロック数を測定する際にひとつ注意しなくてはならないことがある. それは, プロセッサの周波数は, 消費電力の削減のために, 負荷に応じて自動的に調節されているということである. 大雑把に言えば, 負荷が小さければプロセッサの動作周波数は小さくなり, 計算中心の負荷を与えると向上する. つまり, プロセッサが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というコマンドを使うと, プログラム全体のクロック数や命令数など様々なものが いとも簡単に計測できる.

perf stat コマンド
として, 「コマンド」を実行すると, そのプログラムが開始から終了までに要した クロック数や, 実行した命令数が簡単に取得できる. 例: perfコマンドは, プログラム実行中の一部分だけを測定することができない. したがってプログラム中で, SIMD化されている部分の性能だけを測ったり, 並列化されている部分の性能だけを測るなど, 一部分だけを精密に測ることはできない. しかし, 手軽でありながら, 上記に表示されているとおり, 命令数, 分岐命令の数や 分岐予測ミスなど, 有用な情報を多数表示してくれる, という利点がある. 実は計測できるのは上記で表示されている指標にとどまらない. キャッシュミスなど, プログラムの性能解析に必須のその他の情報も表示してくれる. perf listで, 測定できる指標の一覧を 取得でき, perf stat -e 指標名 -e 指標名 ... コマンドで, 測定する指標を指定できる.

perfコマンドは, referenceクロック数と, プロセッサのクロック数の両方を 計測, 表示できる. 前者はperf stat-e ref-cycles, 後者は-e cyclesという イベント名を指定することで取得できる.

練習問題2

IKEHIKO トルコ製ウィルトンラグカーペット アザレア/2371729 グレー/133×190cm

  1. 02mm/mm.ccで, 行列積計算の前後に_rdcyc() の呼び出しを挿入し, プロセッサのクロック数(以下クロック数) を測定せよ. そして, クロック数, 1クロックあたりのflops数, 最内ループの1 iteration (c += A(i,k) * B(k,j)一回)あたりの クロック数を表示せよ.
  2. 03nbody/nbody.ccで, 相互作用計算 (interact_all()の呼び出し)の前後に _rdcyc() の呼び出しを挿入し, クロック数を測定せよ. そして, クロック数, 1 相互作用 (interact2関数一回) あたりの クロック数を表示せよ.
この数字が, 以下の高速化の基本性能となる.
■ハンマー オールステンレス 自在 ゴムB車 200mm 300SRB200(3929515)

結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他

ロールスクリーン オーダー 調光 ビジック デコラ ノブレス 幅121〜160cm 高さ201〜240cm

説明用例題

いきなり問題に入る前に, 簡単な例題を通して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 以下にある.

SIMD化されていないコードの性能

このプログラムを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/iter
5368708800 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化の方針と実際

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
	# ----------- 

SIMD化されたコードの性能の理解

さてここで得られた 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化されたコードのさらなる高速化(命令レベル並列性の向上)

ここまででやっと, なぜ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倍に増やしているにもかかわらず, である. 先ほどのケースと, 今回のケースで, プロセッサ内でおきていることの違いを 図に示すと以下のようになる.
クロック 01234 56789 1011121314
x 0


1



2



クロック 01234 56789 1011121314
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


青色の数字で埋まっているセルは, その変数に対する計算結果が出るクロックを示している. ある変数に対するj回目の更新は, その変数に対する(j-1)回目の結果がでなくては始められない (直前の結果に依存している)ので, 一つの変数に対する計算が, 5クロック/回以上のペースで進行することはない.

そのことは変数がいくつあろうと同じなのだが, 異なる変数に対する計算の間には, 依存関係がないため, 複数をオーバーラップさせることができる. vfmaddの演算器が2つあるため, 同じクロックに二個まで発効することができる.

では最大性能を出すには? もうわかったと思うが変数を10個にしてやれば, すべてのクロックで2つのvfmaddが発行される. 10個のvfmaddを発行するのに 5クロックかかるがそのころには(5クロック経過しているので)先に出した結果が 返ってきて, 次のiterationへと(隙間なく)実行が続いて行く.

クロック 01234 56789 1011121314
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 命令が実行できている --- すなわち最大性能 --- ということになる.

実際にやってみるとその通りになる.

サンドビック T-Max P 旋削用ネガ・チップ CNMG160608PM ×10個セット (4325) [CNMG 16 06 08-PM][r20][s9-831]

結婚祝い ONE) アズワン(AS パンチングバット 1個 特大 その他

グレステンTタイプ 牛刀 724TK 24cm

上記で学んだことを活かして,

  1. 02mm/mm.cc を1コアでできるだけ高速化し, 性能を測定せよ (性能の単位: flops/clock). さしあたり行列サイズや, 行列積の繰り返し数などは自分の好きなように設定して良い. また, M, N, Kのいずれかが適当な数(たとえば8)の倍数でなくてはならない, などのちょっとした制限は適宜導入して良い. 最内ループの1 iterationあたりのクロック数を計測して, 理にかなっているかどうか考察せよ.
  2. 03nbody/nbody.cc を1コアでできるだけ高速化し, 性能を測定せよ (性能の単位: ppi/clock. ppi = particle-particle interaction. 粒子対相互作用). 粒子数 n は自分の好きなように設定して良い. それが適当な数(たとえば8)の倍数でなくてはならない, などのちょっとした制限は適宜導入して良い. 1粒子対あたりの計算のクロック数を計測して, 理にかなっているかどうか考察せよ.

なお, nbodyの問題においては, ベクトル化をする際に, 隣り合う粒子の座標(たとえばx座標)が, 連続したアドレスにオカれていないということが問題となる. これを解消するために, データを並べ替える(8粒子のx座標が連続して並んだようなデータにする)必要がある.

マルチコア並列化

【代引不可】【個人宅配送不可】河村(カワムラ) 盤用キャビネット FX FX 7014-20K[KWD27828]

例題

以下は, $x^2 - 2xy + 3y^2 < 1$ を満たす図形(楕円)の面積を求めるものである. 具体的には, $f(x,y)$を, $(x,y)$が上記図形に入っているときに1, 入っていないときに0となる関数として,
$$ \int_{[-2,2] \times [-2,2]} f(x,y) dx dy $$
を計算するものである. これをOpenMPで並列化するのは簡単で, とするだけでよい. 授業で述べたとおり, が, 複数のスレッドを作りそれらが皆, Sを実行するという構文. は, for文のiterationをスレッド間で分け合って実行する構文である. は両者を合体させたもので, と同じ意味. reduction(+:S) が意味するところを説明する. まず, reduction(+:S) なし(以下)で実行すると, 答えが正しく求まらない. その理由は, 複数のスレッドが, を実行する際に, 複数のスレッドがSを更新するため, 競合状態(授業スライド参照)がおき, Sの更新が正しく行われなくなるからである. reduction(+:S)は, という動作をする.
アズワン(AS ONE) 温湿度計 白 校正証明書付 A-230-W

課題2 (マルチコア並列化)

(送料別途)(直送品)護国 鬼飾り 主棟用 BM-15(耳付型) (0.35mm) ステン・基準色 (2個入)
  1. 02mm/mm.cc を1ノード (マルチコア)でできるだけ高速化せよ. 行列サイズや, 行列積の繰り返し数などは自分の好きなように設定して良く, 適当な数の倍数でなくてはならない, などの制限は適宜導入して良い.
  2. ブーツ ニット ソックスブーツ を1ノード (マルチコア)でできるだけ高速化せよ. 粒子数 n は自分の好きなように設定して良いく, 適当な数の倍数でなくてはならない, などの制限は適宜導入して良い.
( 特注 納期約4-6週 ) プロギア 02 アイアン 単品販売 5I ツアー AD アイアン シャフト メンズ PRGR 202

複数ノード並列化

TRUSCO イーグルワゴン 900X600XH740 3段 YG色 EGW-793-YG≪代引不可≫

オプション課題 (複数ノード並列化)

目次へ戻る
アズワン(AS ONE) ダイアモンドチップ DL10T Tipack 未滅菌 0.1〜20μL 96本×10箱入 F171200 1箱(96本×10ラック入り)

提出課題

チェア チェアー おしゃれ キャスター オフィスチェア パソコン オフィス 椅子 コンパクト デザイン リモート テレワーク 在宅勤務 ロウヤ LOWYA

上記の課題1, 2および余力に応じてオプション課題などをやって, 行列積, N体問題それぞれをどう高速化したか, どのくらい高速化出来たかをまとめて下さい. 単に「こうやったらこれだけ速くなった」ではなく, 「こうしたときの期待される性能はこう(例: このループ一回何クロック) である, やってみたらこういう結果になった (ちゃんと一致した)」ということを書いて下さい. もちろん期待される性能が出ないこともあるとおもいますが, ベストを尽くして下さい.

SOYOFERTA.COM RSS