38
Intel AVX で SIMD 入門 2012/9/15 (Sat) 2闇鍋プログラミング勉強会 Yuki Kawaguchi 1

Yaminabe simd

Embed Size (px)

DESCRIPTION

2012/09/15 第2回 闇鍋プログラミング 発表資料

Citation preview

Page 1: Yaminabe simd

Intel AVX で SIMD 入門

2012/9/15 (Sat)第2回 闇鍋プログラミング勉強会

Yuki Kawaguchi

1

Page 2: Yaminabe simd

自己紹介

2

名前: Yuki Kawaguchitwitter: @kawa0810はてな id: kawa0810

・学生時代の研究並列・分散処理,GPGPU,数値計算関係

・仕事 orzバックアップソフトの開発・サポート

Page 3: Yaminabe simd

自己紹介

3

名前: Yuki Kawaguchitwitter: @kawa0810はてな id: kawa0810

・学生時代の研究並列・分散処理,GPGPU,数値計算関係

・仕事バックアップソフトの開発・サポート

Page 4: Yaminabe simd

本題

4

Page 5: Yaminabe simd

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 を逐次計算する場合

Page 6: Yaminabe simd

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 を逐次計算する場合

Page 7: Yaminabe simd

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 を逐次計算する場合

Page 8: Yaminabe simd

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 を逐次計算する場合

Page 9: Yaminabe simd

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 を逐次計算する場合

Page 10: Yaminabe 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 演算する場合

Page 11: Yaminabe 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 演算する場合

Page 12: Yaminabe 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 演算する場合

Page 13: Yaminabe 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 演算する場合

Page 14: Yaminabe 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 演算が使用できないケース

− × /

Page 15: Yaminabe 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 演算が使用できないケース

− × /

Page 16: Yaminabe simd

SIMD 演算器

8

マルチコアプロセッサ内の各コアごとにSIMD 演算器と専用レジスタを搭載している!!

CoreSIMD 演算器

専用レジスタ

Core Core

Core Core

各 CPU 毎に適した API を使用してプログラミングする必要がある!!

Page 17: Yaminabe simd

SIMD の重要性

9

世界1位のスパコン:Sequoia世界2位のスパコン:K (京)にも SIMD 演算器が搭載!!

Pentium III からSIMD 演算器が搭載

Intel,AMD,Cell/B.Eなど家庭向け CPU にも SIMD 演算器が搭載!!

高い性能を出すためにSIMD の利用が必須!!

Page 18: Yaminabe 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 演算可能に!!

Page 19: Yaminabe simd

Intel AVX で SIMDプログラミング

11

Page 20: Yaminabe simd

今回のサンプル問題

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 は動きません!!

Page 21: Yaminabe simd

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;}

Page 22: Yaminabe simd

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 境界にそろえて確保する

Page 23: Yaminabe simd

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() で解放する

Page 24: Yaminabe simd

メモリアライメントの方法

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 で指定

Page 25: Yaminabe simd

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 演算可能だが,

データのロードにオーバーヘッドが発生する!!

Page 26: Yaminabe 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 に格納する)

Page 27: Yaminabe simd

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() を使うとメモリアライメントを意識することなく結果の格納が可能だが,データのストアにオーバーヘッドが発生する!!

Page 28: Yaminabe simd

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

Page 29: Yaminabe simd

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 の場合

Page 30: Yaminabe simd

疑問1: 端数がでる場合の対処方法

20

ゼロパディング 端数のみ逐次計算

�����������

12345670

�����������

0を挿入しすべてSIMD 演算で行う

���������

1234567

���������

赤い四角内は逐次計算

Page 31: Yaminabe simd

疑問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];}

Page 32: Yaminabe simd

疑問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 に変わる

Page 33: Yaminabe simd

更なる高速化を目指して

23

Page 34: Yaminabe simd

キャッシュの重要性

24

・キャッシュヒット効率を向上させれば 性能向上が期待できる・SSE・AVX にはキャッシュ制御関数がある

・_mm_prefetch(): データをキャッシュに読み込む・_mm256_stream_ps(): キャッシュを介さず結果を格納・_mm256_stream_pd(): 同上(倍精度版)

Page 35: Yaminabe simd

今回の問題の適応

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

+=

計算結果を再利用しない キャッシュを介さずメモリに結果を返す

Page 36: Yaminabe simd

今回の問題の適応

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

+

=

計算結果を再利用しない キャッシュを介さずメモリに結果を返す

Page 37: Yaminabe simd

_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];}

Page 38: Yaminabe simd

今後の SIMD

27

・Fused Multiply Add (FMA) 命令1クロックで浮動小数点の積和算を行う

z = α × x + y -> Haswell で実装予定

・Boost.SIMD

・Many Integrated Core (MIC) の登場