Upload
tomohiro-namba
View
91
Download
3
Embed Size (px)
Citation preview
C++ For Researchers 研究生のためのC++
企画・立案 難波知宏
プログラムの高速化
第七回
2
プログラム改良のステップ
3
原因の特定
高速化① 最適化
高速化② アルゴリズムの改良
高速化③ マルチスレッド
高速化④ GPUアクセラレーション
ボトルネックの特定
第一節
4
Visual Studioのパフォーマンスエクスプローラを使ってみよう
5
Exercise 7-1
プログラムのどの部分にどれくらいの負荷がかかっているか調べること
ができるツール
6
パフォーマンスエクスプローラー
7
パフォーマンスエクスプローラーの使い方
8
パフォーマンス測定結果の見方
どの関数にどれくらい
の負荷がかかっている
かわかる
概要ビューで全体が わかる
9
パフォーマンス測定結果の見方
プログラムのどこが
遅いかわかる
関数の詳細ビューで ボトルネックの場所が わかる
最適化
第二節
10
最適化
11
コンパイラで最適化(自動)
• コンパイルオプションを変えることで
コンパイラが最適化を行う
• とりあえず楽。
プログラマが最適化(手動)
• プログラマがプログラムをいじって
手動で最適化を行う
• めんどくさい。
• コンパイラオプションで最適化して、
まだ遅いときにやろう
12
コンパイラによる最適化有効
ビルド構成をDebugモード
から
Releaseモードにする
SIMD拡張命令セット (Single Instruction Multi Data)
一つの命令で複数のデータを扱う (float×8やdouble×4の演算を一回の命令でやる)
13
Intel SSE / AVX
1.0 2.0 3.0 4.0
2.0 4.0 6.0 8.0
+
↓
専用レジスタ
専用レジスタ
1.0 2.0 3.0 4.0
64×4=256bit
14
Intel SSE / AVXの有効化
プロジェクトのプロパティ > C/C++ > コード生成の拡張命令 セットを変更 でIntel SSE/AVXの有効化
sample 7-05
#include <immintrin.h> #include <iostream> using namespace std; void main() { __m256d a = _mm256_set_pd(1.0, 2.0, 3.0, 4.0); __m256d b = _mm256_set_pd(1.0, 2.0, 3.0, 4.0); __m256d c = _mm256_add_pd(a, b); cout << c.m256d_f64[0] << endl; cout << c.m256d_f64[1] << endl; cout << c.m256d_f64[2] << endl; cout << c.m256d_f64[3] << endl; }
実行結果
8
6
4
2
15
z
Intel SSE / AVXの手動プログラミング
16
ただし
自動ベクトル化
最近のコンパイラは使えそうなところは自動でSIMD
命令を使う Visual C++の自動ベクトル化の説明
https://msdn.microsoft.com/ja-jp/library/hh872235.aspx
自動ベクトル化はどんな時に行われるか
http://www.isus.jp/products/c-compilers/compiler_part4/
SIMD命令をgccのオートベクタライズの最適化で使う方法
http://blog.kmckk.com/archives/4484762.html
17
自動ベクトル化
最近のコンパイラは使えそうなところは自動でSIMD
命令を使う Visual C++の自動ベクトル化の説明
https://msdn.microsoft.com/ja-jp/library/hh872235.aspx
自動ベクトル化はどんな時に行われるか
http://www.isus.jp/products/c-compilers/compiler_part4/
SIMD命令をgccのオートベクタライズの最適化で使う方法
http://blog.kmckk.com/archives/4484762.html
18
基本コンパイラ任せ
で良さげ
高速化のために、まずは
• 最適化オプションをつける
• 拡張命令セットを有効にする
19
ここまでのまとめ
アルゴリズムの改良
第三節
20
sample 7-06
static int a[length];
static int b[length];
static int c[length];
for (int i = 0; i < length; i++)
{
c[i] = a[i] + b[i];
}
sample 7-07
struct int3 { int a, b, c; };
static int3 x[length];
for (int i = 0; i < length; i++)
{
x[i].c = x[i].a + x[i].b;
}
21
アルゴリズムの改良
①キャッシュミスを減らせ
どっちのプログラムが早いだろうか
sample 7-06
static int a[length];
static int b[length];
static int c[length];
for (int i = 0; i < length; i++)
{
c[i] = a[i] + b[i];
}
sample 7-07
struct int3 { int a, b, c; };
static int3 x[length];
for (int i = 0; i < length; i++)
{
x[i].c = x[i].a + x[i].b;
}
22
アルゴリズムの改良
①キャッシュミスを減らせ
答え 右
(左: 70msくらい)
(右: 50msくらい)
CPU-メモリ間
23
なぜ右のプログラムの方が早いのか?
高速 低速
CPU-メモリ間
24
なぜ右のプログラムの方が早いのか?
高速 低速
一回アクセスした領域
は、その周辺も含めて
キャッシュメモリに記憶
飛び飛びにアクセスすると、
キャッシュがころころ入れ替わり、
キャッシュヒット率が低下する
for (int i = 0; i < 1024; i++)
{
// …
}
// …
for (int i = 0; i < 1024; i++)
{
// …
}
for (int i = 0; i < 1024; i++)
{
// …
// …
}
25
②ループを減らせ
#include "Eigen¥Dense.hpp"
using namespace Eigen;
void main()
{
MatrixXd A;
}
#include "Eigen¥Sparse.hpp"
using namespace Eigen;
void main()
{
SparseMatrix<float> A;
}
26
③余計な計算を減らせ
スパース行列
日頃から次に気をつけてプログラムを組む
• キャッシュヒット率が上がりやすいプログラムにする
• ループの数を減らす
• 余分な計算を減らす
27
ここまでのまとめ
マルチスレッド
第四節
28
マルチコアCPUだと、異なるCPUコアで同時実行ができる
マルチスレッドプログラミングの方法
• OSのマルチスレッドAPIを使う (Win32APIだとCreateThread関数)
• マルチスレッドライブラリを使う (標準ライブラリ, IntelTBB等)
• 言語拡張(OpenMP)を使う
29
マルチスレッド
複数の処理を並列に実行する
#pragma omp … を既存のプログラムに挿入する形で利用
↓
プログラムをあまり修正しなくていいので導入が簡単
コンパイルオプションでOpenMPを無効にすれば、
シングルスレッドのプログラムにすぐに直せる (もとに戻すのが簡単)
OSに依存しないので移植性が高い
30
OpenMP
マルチスレッド
sample 7-01
// Calculation. float sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0; #pragma omp parallel sections { #pragma omp section { for (int i = 0; i < 2500000; i++) { sum1 += data[i]; } } #pragma omp section … } float sum = sum1 + sum2 + sum3 + sum4;
sample 7-02
// Calculation.
float sum = 0;
{
for (int i = 0; i < length; i++)
{
sum += data[i];
}
}
31
OpenMP プログラム例1 配列の総和を求める
sample 7-01
// Calculation. float sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0; #pragma omp parallel sections { #pragma omp section { for (int i = 0; i < 2500000; i++) { sum1 += data[i]; } } #pragma omp section … } float sum = sum1 + sum2 + sum3 + sum4;
sample 7-02
// Calculation.
float sum = 0;
{
for (int i = 0; i < length; i++)
{
sum += data[i];
}
}
32
OpenMP プログラム例1 配列の総和を求める
配列を4等分して
それぞれ別々に総
和を計算する
素直なやり方
sample 7-01 実行結果
sum is 10000000.000000. 5 ms.
sample 7-02 実行結果
sum is 10000000.000000.
13 ms.
33
OpenMP プログラム例1 配列の総和を求める
配列を4等分して
それぞれで総和を
計算する
素直なやり方
スピードUP
34
OpenMPの有効化
C/C++>言語>OpenMPの
サポートを「はい」にすると
OpenMPが有効になる
sample 7-09
#include <iostream> void main() { #pragma omp parallel for for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { printf("%d¥n", i); } } }
実行結果
Thread is 0 Thread is 3 Thread is 3 Thread is 3 Thread is 3 Thread is 2 Thread is 2 Thread is 2 Thread is 2 Thread is 1 Thread is 1 Thread is 1 Thread is 1 Thread is 0 Thread is 0 Thread is 0
35
OpenMP プログラム例2 for文の並列化
OpenMPで行列積を高速化しよう
36
Exercise 7-2
GPUアクセラレーション
第五節
37
38
時間足りませんでした
39
テンプレート引数で、
型、行数、列数を指定
サイズは 実数のとき固定サイズ
Xのとき可変サイズ