Upload
yuki-kawaguchi
View
3.414
Download
5
Embed Size (px)
DESCRIPTION
2012/09/15 第2回 闇鍋プログラミング 発表資料
Citation preview
Intel AVX で SIMD 入門
2012/9/15 (Sat)第2回 闇鍋プログラミング勉強会
Yuki Kawaguchi
1
自己紹介
2
名前: Yuki Kawaguchitwitter: @kawa0810はてな id: kawa0810
・学生時代の研究並列・分散処理,GPGPU,数値計算関係
・仕事 orzバックアップソフトの開発・サポート
自己紹介
3
名前: Yuki Kawaguchitwitter: @kawa0810はてな id: kawa0810
・学生時代の研究並列・分散処理,GPGPU,数値計算関係
・仕事バックアップソフトの開発・サポート
本題
4
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
5
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ? ...
x
y
z
+
=
配列 x と配列 y を逐次計算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
5
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ...
x
y
z
+
=配列 x と配列 y を逐次計算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
5
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ...
x
y
z
+
=配列 x と配列 y を逐次計算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
5
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ...
x
y
z
+
=
配列 x と配列 y を逐次計算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
5
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
x
y
z
+
=
配列 x と配列 y を逐次計算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
6
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ? ...
x
y
z
+
=配列 x と配列 y を SIMD 演算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
6
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ...
x
y
z
+=
配列 x と配列 y を SIMD 演算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
6
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ...
x
y
z
+
=
配列 x と配列 y を SIMD 演算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
6
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
...
x
y
z
+
=
配列 x と配列 y を SIMD 演算する場合
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
7
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ? ...
x
y
z
+
=SIMD 演算が使用できないケース
− × /
5 8 5 9 7 17 3 14 7 11 9 ...
SIMD とは?
7
・Single Instruction Multiple Data・1回の命令で複数のデータを処理する命令形式
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ? ...
x
y
z
+
=SIMD 演算が使用できないケース
− × /
SIMD 演算器
8
マルチコアプロセッサ内の各コアごとにSIMD 演算器と専用レジスタを搭載している!!
CoreSIMD 演算器
専用レジスタ
Core Core
Core Core
各 CPU 毎に適した API を使用してプログラミングする必要がある!!
SIMD の重要性
9
世界1位のスパコン:Sequoia世界2位のスパコン:K (京)にも SIMD 演算器が搭載!!
Pentium III からSIMD 演算器が搭載
Intel,AMD,Cell/B.Eなど家庭向け CPU にも SIMD 演算器が搭載!!
高い性能を出すためにSIMD の利用が必須!!
SIMD 命令の種類
10
PowerPC 系・AltiVec・128bit 単位で SIMD 演算する
Intel (Sandy Bridge 以前)・Streaming SIMD Extentions (SSE) 系・128bit 単位で SIMD 演算する・GCC4.5 以上の場合 -O3 で自動並列化(精度保証無)
Intel (Sandy Bridge 以降)・Advanced Vector eXtentions (AVX) が追加・128bit もしくは 256bit 単位で SIMD 演算可能に!!
Intel AVX で SIMDプログラミング
11
今回のサンプル問題
12
5 8 5 9 7 17 3 14 7 11 9 ...
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ? ...
x
y
z
+ + + + + + + + + + + ...
|| || || || || || || || || || || ...
任意の大きさの配列 x と配列 y の加算結果を配列 z に格納する
言語 Cコンパイラ gcc4.7コンパイルオプション
-std=c11-O2 -mavx
*注意) 第2世代 Intel Core i シリーズ以上じゃないと AVX は動きません!!
Step1: メモリアライメント
13
#include <stdio.h>#include <stdlib.h>#include <time.h>#include <immintrin.h>//AVX: -mavx
size_t const align = 32;void vec_add(const size_t n, float* z, const float* x, const float* y);
int main(void){ const size_t n = 1024; float* x = (float*)_mm_malloc(n * sizeof(float), align); float* y = (float*)_mm_malloc(n * sizeof(float), align); float* z = (float*)_mm_malloc(n * sizeof(float), align);
srand( (unsigned)time(NULL) ); for(size_t i=0; i<n; ++i) x[i] = (float)rand() / RAND_MAX; for(size_t i=0; i<n; ++i) y[i] = (float)rand() / RAND_MAX; for(size_t i=0; i<n; ++i) z[i] = 0.0;
vec_add(n, z, x, y); _mm_free(x); _mm_free(y); _mm_free(z);
return 0;}
Step1: メモリアライメント
13
#include <stdio.h>#include <stdlib.h>#include <time.h>#include <immintrin.h>//AVX: -mavx
size_t const align = 32;void vec_add(const size_t n, float* z, const float* x, const float* y);
int main(void){ const size_t n = 1024; float* x = (float*)_mm_malloc(n * sizeof(float), align); float* y = (float*)_mm_malloc(n * sizeof(float), align); float* z = (float*)_mm_malloc(n * sizeof(float), align);
srand( (unsigned)time(NULL) ); for(size_t i=0; i<n; ++i) x[i] = (float)rand() / RAND_MAX; for(size_t i=0; i<n; ++i) y[i] = (float)rand() / RAND_MAX; for(size_t i=0; i<n; ++i) z[i] = 0.0;
vec_add(n, z, x, y); _mm_free(x); _mm_free(y); _mm_free(z);
return 0;}
計算する領域を 32byte 境界にそろえて確保する
Step1: メモリアライメント
13
#include <stdio.h>#include <stdlib.h>#include <time.h>#include <immintrin.h>//AVX: -mavx
size_t const align = 32;void vec_add(const size_t n, float* z, const float* x, const float* y);
int main(void){ const size_t n = 1024; float* x = (float*)_mm_malloc(n * sizeof(float), align); float* y = (float*)_mm_malloc(n * sizeof(float), align); float* z = (float*)_mm_malloc(n * sizeof(float), align);
srand( (unsigned)time(NULL) ); for(size_t i=0; i<n; ++i) x[i] = (float)rand() / RAND_MAX; for(size_t i=0; i<n; ++i) y[i] = (float)rand() / RAND_MAX; for(size_t i=0; i<n; ++i) z[i] = 0.0;
vec_add(n, z, x, y); _mm_free(x); _mm_free(y); _mm_free(z);
return 0;}
計算する領域を 32byte 境界にそろえて確保する
_mm_malloc() で確保した領域は必ず _mm_free() で解放する
メモリアライメントの方法
14
■ 静的に確保する: GCC の場合float __attribute__(aligned(align)) tmp[size];
■ 動的に確保する: _mm_malloc()float* tmp = (float*)_mm_malloc(size, align)
ただし,_mm_free(tmp) で解放する必要がある
■ 動的に確保する: posix_memalign()posix_memalign((void**)&tmp, align, size);
free(tmp) で解放することが可能!!メインストリーム環境なら動くので無難
align はメモリ上に揃えたい境界を byte で指定
Step2: 専用レジスタにロード
15
extern size_t const malign;
void vec_add(const size_t n, float* z, const float* x, const float* y){ static const size_t s_size = malign / sizeof(float); for(size_t i=0; i<n; i+=s_size){ //1行で書いてOK
__m256 tmp = _mm256_add_ps(_mm256_load_ps(x+i), _mm256_load_ps(y+i)); _mm256_store_ps(z+i, tmp); }} _mm256_load_ps() で
SIMD 専用レジスタにデータをロード
_mm256_loadu_ps() を使うとメモリアライメントを意識することなく SIMD 演算可能だが,
データのロードにオーバーヘッドが発生する!!
Step3: SIMD 演算器で計算
16
extern size_t const malign;
void vec_add(const size_t n, float* z, const float* x, const float* y){ static const size_t s_size = malign / sizeof(float); for(size_t i=0; i<n; i+=s_size){ //1行で書いてOK
__m256 tmp = _mm256_add_ps(_mm256_load_ps(x+i), _mm256_load_ps(y+i)); _mm256_store_ps(z+i, tmp); }} _mm256_add_ps() で加算
(今回は結果を tmp に格納する)
Step4: 結果を返す
17
extern size_t const malign;
void vec_add(const size_t n, float* z, const float* x, const float* y){ static const size_t s_size = malign / sizeof(float); for(size_t i=0; i<n; i+=s_size){ //1行で書いてOK
__m256 tmp = _mm256_add_ps(_mm256_load_ps(x+i), _mm256_load_ps(y+i)); _mm256_store_ps(z+i, tmp); }}
_mm256_store_ps() で結果を返却z+i はキャッシュに保持される
_mm256_storeu_ps() を使うとメモリアライメントを意識することなく結果の格納が可能だが,データのストアにオーバーヘッドが発生する!!
SIMD 演算の手順のまとめ
18
メモリアライメントを揃えて領域を確保する
SIMD 専用キャッシュにデータをロード
計算する
結果を返却
・_mm_malloc・posix_memalign・etc
・_mm256_load_ps・_mm256_loadu_ps
・_mm256_store_ps・_mm256_storeu_ps
・_mm256_add_ps・_mm256_mul_ps・etc
Mac はコンパイルに注意!!
19
$ gcc-4.7 simd.c -std=c11 -O2 -mavx
$ gcc-mp-4.7 -S simd.c -std=c11 -O2 -mavx$ clang-mp-3.2 simd.s -mavx
Linux とか
Mac の場合
疑問1: 端数がでる場合の対処方法
20
ゼロパディング 端数のみ逐次計算
�
�����������
12345670
�
�����������
0を挿入しすべてSIMD 演算で行う
�
���������
1234567
�
���������
赤い四角内は逐次計算
疑問1: 端数のみを逐次計算する方法
21
extern size_t const malign;
void vec_add(const size_t n, float* z, const float* x, const float* y){ static const size_t s_size = malign / sizeof(float); const size_t end = (n / s_size) * s_size; for(size_t i=0; i<end; i+=s_size){ //1行で書いてOK
__m256 tmp = _mm256_add_ps(_mm256_load_ps(x+i), _mm256_load_ps(y+i)); _mm256_store_ps(z+i, tmp); }
//端数部分 for(size_t i=end; i<n; ++i) z[i] = x[i] + y[i];}
疑問2: 倍精度演算の方法
22
size_t const malign = 32;
void vec_add(const size_t n, double* z, const double* x, const double* y){ static const size_t s_size = malign / sizeof(double); const size_t end = (n / s_size) * s_size; for(size_t i=0; i<end; i+=s_size){ //1行で書いてOK
__m256d tmp = _mm256_add_pd(_mm256_load_pd(x+i), _mm256_load_pd(y+i)); _mm256_store_pd(z+i, tmp); }
//端数部分 for(size_t i=end; i<n; ++i) z[i] = x[i] + y[i];}
・__m256・_mm256_add_ps・_mm256_store_ps
__m256d_mm256_add_pd_mm256_store_pd
ps が pd に変わる
更なる高速化を目指して
23
キャッシュの重要性
24
・キャッシュヒット効率を向上させれば 性能向上が期待できる・SSE・AVX にはキャッシュ制御関数がある
・_mm_prefetch(): データをキャッシュに読み込む・_mm256_stream_ps(): キャッシュを介さず結果を格納・_mm256_stream_pd(): 同上(倍精度版)
今回の問題の適応
25
5 8 5 9 7 17 3 14 7 11 9 ...
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ? ? ? ? ...
x
y
z
+=
計算結果を再利用しない キャッシュを介さずメモリに結果を返す
今回の問題の適応
25
5 8 5 9 7 17 3 14 7 11 9 ...
3 1 4 1 5 9 2 6 5 3 5 ...
2 7 1 8 2 8 1 8 2 8 4 ...
? ? ? ? ? ? ? ...
x
y
z
+
=
計算結果を再利用しない キャッシュを介さずメモリに結果を返す
_mm256_stream_ps 適応版
26
extern size_t const malign;
void vec_add(const size_t n, float* z, const float* x, const float* y){ static const size_t s_size = malign / sizeof(float); const size_t end = (n / s_size) * s_size; //キャッシュを介さずに直接メモリに書き込む for(size_t i=0; i<end; i+=s_size){ _mm256_stream_ps(z+i, _mm256_add_ps(_mm256_load_ps(x+i), _mm256_load_ps(y+i)) ); }
//端数部分 for(size_t i=end; i<n; ++i) z[i] = x[i] + y[i];}
今後の SIMD
27
・Fused Multiply Add (FMA) 命令1クロックで浮動小数点の積和算を行う
z = α × x + y -> Haswell で実装予定
・Boost.SIMD
・Many Integrated Core (MIC) の登場