©︎

スーパーコンピュータが組込みシステムに降りてくる！
〜新時代の高性能組込みシステムのSIMD／ベクトル処理の要点を押さえる
北九州市立大学 山崎 進

2021 Susumu Yamazaki

1

©︎

背景
• スーパーコンピュータで培われてきた並列計算の技術が組込みシステムに降りてきたのは，今に始まったことではない• 1994年発売の初代PlayStation (右図)
• 2000年発売のPlayStation 2©MarcelBuehner, Creative Commons

2021 Susumu Yamazaki

2

©︎

背景
• 2021年3月発表 次期ARMアーキテクチャとなるArmv9アーキテクチャ• スーパーコンピュータ富岳で用いられるベクトル命令SVE/SVE2が採用• RISC-V• ベクタ拡張を搭載したD1 chip搭載のIoTボードが市販• 2021年6月 ベクタ拡張を搭載したRISC-Vチップを含む高性能RISC-VチップをIntelが作ると発表

2021 Susumu Yamazaki

3

©︎

背景
• SIMD(Single Instruction Multiple Data) についてはこれに先んじて普及• 広く普及している現行のArmv8アーキテクチャにSIMDが搭載• GPUも基本的にはSIMDアーキテクチャ• Arm MaliやNVIDIA JetsonのようにGPUを搭載したIoTも一般的

2021 Susumu Yamazaki4©︎

ハイエンドの組込みシステムに搭載されるSIMD／ベクトル処理機能を活用するには？

2021 Susumu Yamazaki5©︎

ハイエンドの組込みシステムに搭載されるSIMD/ベクトル処理機能を活用するには• 一般的な答え• スーパーコンピュータで長年培われてきた技術に由来するBLAS/LAPACKという線形代数に基づくライブラリと，それを活用するOSSライブラリを利用しましょう• その理由• BLAS/LAPACKは，人知のかぎりを尽くしてSIMD／ベクトル処理機能を極限まで活用すべくチューニングされている• これを超えるようなものを容易に開発することはできない「金字塔」
2021 Susumu Yamazaki6©︎

ハイエンドの組込みシステムに搭載されるSIMD/ベクトル処理機能を活用するには• BLAS/LAPACKのようなライブラリを活用するときに組込みエンジニアが持つであろう
懸念・要望• きっとメモリ・ストレージのサイズがかかるだろう• 原理や仕組みがわからないと安心して使えない

2021 Susumu Yamazaki7©︎

ハイエンドの組込みシステムに搭載されるSIMD/ベクトル処理機能を活用するには• そこで，本分科会では，次のことをレクチャーします• SIMD／ベクトル処理を活用する上で基礎となるSIMD(Single Instruction Multiple Data)の考え方• これを生かすためのメモリ配置• SIMD命令・ベクトル命令活用の要点• BLASのAPIのごく一部を例にして読み取った，SIMD／ベクトル処理プログラミングのヒント• 逆に扱わないこと• BLAS/LAPACKの活用方法について

2021 Susumu Yamazaki8©︎

私自身はまだまだ研究の途上で達人の域には達していない
今後，見解を変えるかもしれないことをご了承ください(私の見た「達人」は，もっともっと，すごかったです！)

2021 Susumu Yamazaki

9

©︎

では本題

2021 Susumu Yamazaki

10©︎

SIMDの考え方

2021 Susumu Yamazaki

11

©︎

並列処理の分類• SISD(シスディ: Single Instruction Single Data: 単一命令単一データ): 並列ではない• SIMD(シムディ: Single Instruction Multiple Data: 単一命令複数データ)• MISD(ミスディ: Multiple Instruction Single Data: 複数命令単一データ)• MIMD(ミムディ: Multiple Instruction Multiple Data: 複数命令複数データ)

2021 Susumu Yamazaki

12

©︎

MIMD
• MIMD (ミムディー，Multiple Instruction Multiple Data):
1つ1つが異なる動作ができる計算ユニットに並列処理させる方式• たとえるなら，狩をするさいの狩猟犬のように，各々が連携しながら個別の動きをする方式• マルチコアCPUといったときには，1つ1つのコアが独立して個別の動きをすることができる• MIMDはSIMDと比べて並列度を稼ぎにくい• 市販されているCPUでは数十〜百数十コアがせいぜい• 研究段階では千個以上のコアを持つものもある

2021 Susumu Yamazaki

13©︎SIMD• SIMD(シムディ, Single Instruction Multiple Data)
同じ動作をする計算ユニットに並列処理させる方式• たとえるなら田植えをたくさんの人数で同時に行うようなもの• CPU の SIMD 命令やベクタ命令，GPUで採用• SIMD方式だと並列度を上げやすく，1000以上の並列度を持つGPUが市販されている

2021 Susumu Yamazaki

14

©︎

CPUのSIMD命令• 固定長のビット幅のSIMDレジスタで演算を行う• 利点: 実現が容易• Intel SSEならば128ビット• たとえば整数の加減算命令であれば，区切ったビット位置で加算回路のキャリー／ボローを伝播させないようにすれば，実現できる• Intel AVX / AVX2ならば256ビット• 欠点:• Intel AVX-512ならば512ビット• クロック周波数の向上の場合と異なり，SIMD命令を利用しないと• ARM NEON ならば128ビット(ARMv8の場合)性能は上がらないのでアセンブリプログラミングもしくはSIMD命令のコード生成に対応したコンパイラが必須• SIMDレジスタを区切って使用する• SIMDレジスタのビット幅が変わると機械語命令の互換性がないの• 整数ならば{符号付き/なし},{8/16/32/64ビット}• 浮動小数点数ならば半精度(16ビット)(ARM NEONのみ)/単精度(32ビット)/倍精度(64ビット)

で，ビット幅を変える時にプログラミングやコンパイルをしなおす必要がある• 最近のClangとGCCは auto vectorization が常時有効となっており，ループに対して自動でSIMD命令を使うようにコード生成してくれる

2021 Susumu Yamazaki

15

©︎

ベクトル命令• ベクトル命令も並列処理の分類だとSIMDに区分される• これに対しベクトル命令では• ベクトルレジスタ幅はCPUによって• SIMD命令の欠点:• SIMDレジスタのビット幅が変わると機械語命令の互換性がないので，ビット幅を変える時にプログラミングやコンパイルをしなおす必要がある

2021 Susumu Yamazaki

異なるが，それに合わせてループ回数などのパラメータを設定する専用命令が存在する• そのためベクトルレジスタ幅が変わっても，プログラミングやコンパイルをしなおす必要はない

16

©︎

ベクトル命令について

2021 Susumu Yamazaki

17

©︎

ベクトル命令について• RISC-Vのベクトル拡張に対応するCPUを搭載したIoTボードを入手できました🎉• ベクタ命令をコンパイルするクロスコンパイル環境の構築に成功しました🎉• ベクタ命令を使ったサンプルアセンブリコードの実行にも成功しました🎉• ただ，ベンチマーク測定の環境を整えるのが間に合いませんでした😭
• そのため，動作確認以上のことは出来ていません(申し訳ないです)😭

2021 Susumu Yamazaki

18

©︎

そういうわけで2021 Susumu Yamazaki

19

©︎

以降，SIMD命令にフォーカスします
ご了承ください 😞

2021 Susumu Yamazaki

20

©︎

ところで2021 Susumu Yamazaki

21

©︎

SIMD化することでどれくらい効果があるのか？

2021 Susumu Yamazaki

22

©︎

サンプルプログラム作ってみました• https://github.com/zacky1972/simd_sample• モノクロ化の画像フィルタを題材に• 8ビット符号なし整数→32ビット符号なし整数→32ビット浮動小数点数→{R, G, B} <= 0.299 * r + 0.587 * g + 0.114 * b→小数点以下四捨五入→32ビット符号なし整数→8ビット符号なし整数• C言語で書いた場合 (2種類: 後述)
Auto-vectorizationのためループでSIMDコードを生成• Intrinsic (SIMD命令をCで記述する方法の1つ)で書いた場合• Intel Core i5 / AVX2
• ARMv8 / NEON
• C言語+Auto-vectorizationに比べてSIMD命令を手で記述して最適化を図ると2〜5倍の高速化効果がある• しかもSIMD命令の記述にはまだ最適化の余地がありそうな感触がある

2021 Susumu Yamazaki

23

©︎

2倍も高速になる 😲
vsたかだか2倍しか高速にならない 🙅

2021 Susumu Yamazaki

24

©︎

たかだか2倍程度しか高速にならないのにIntrinsics / アセンブリコードのプログラミングをするの？🤔

2021 Susumu Yamazaki

25

©︎

後述🤔🤔🤔

2021 Susumu Yamazaki

26

©︎

閑話休題

2021 Susumu Yamazaki

27

©︎

SIMDを生かすためのメモリ配置
2021 Susumu Yamazaki

28

©︎

Array of Structures (AoS)
Structure of Arrays (SoA)
2021 Susumu Yamazaki

29

©︎

Array of Structures (AoS)• 構造体の配列• AoSはSIMD化しにくい• 飛び飛びに参照する必要がある• RGB 8ビットずつだった場合には，3バイト飛ばししながらr, g, bをロードしてモノクロ化の係数をかけてやる必要がある• ただし NEON にはそのための便利なロード・ストア命令が備わっている！

• この場合だと3バイトずつ読み飛ばしてSIMDレジスタに格納していくロード命令 ld3q が用意されている

2021 Susumu Yamazaki

30

©︎

Structure of Arrays (SoA)• 配列の構造体• AoSよりもSoAの方がSIMD化しやすい！
• 8ビット配列の値をSSE・NEONの場合128ビット(16個分)ずつ
AVX2の場合256ビット(32個分)ずつ
AVX-512の場合512ビット(64個分)ずつまとめて処理する形にしやすい

2021 Susumu Yamazaki

31

©︎

ベンチマーク結果をもう一度見てみよう• https://github.com/zacky1972/simd_sample• Intel / AVX2 だと AoSよりSoAが2倍近く速い• SoAの方がSIMD化しやすいという定石通り• ARMv8 / NEONだと微妙にAoSの方が速い• NEONに用意されている3バイト飛ばしのロード・ストア命令の効果か？

• 構造体のサイズが5バイト以上になると対応する命令がなくなるので，おそらく遅くなるのでは？

2021 Susumu Yamazaki

32

©︎

Auto-vectorizationを使う範囲ではAoSよりもSoAのスタイルにメモリ配置した方が無難そう

2021 Susumu Yamazaki

33

©︎

SIMD命令・ベクトル命令活用の要点
2021 Susumu Yamazaki

34

©︎

SIMD命令・ベクトル命令活用の要点• モノクロ化の場合の流れ(これら1つ1つの手順は定石パターンになりそう)• CPUのSIMDレジスタ数を超過しないようにスケジューリングする

1. ロード• 拡張して下位と上位に分割する際に，上位の後続の計算を後回しにする

2. 8ビットから16ビットに拡張して下位と上位に分割• R, Gの処理を先にやって，積→積和してSIMDレジスタをまとめた後 Bの処理を行なって積和する

3. 16ビットから32ビットに拡張して下位と上位に分割
4. 32ビット整数から32ビット浮動小数点数に変換
5. 積→積和→積和• 変数に型を明記するとわかりやすい (ハンガリアン記法的なアプローチ)• 例:• float32x4_t f32x4_pixel_r;

6. 32ビット浮動小数点数を四捨五入して32ビット整数に変換
7. 32ビットから16ビットに縮小しつつ下位と上位を統合
8. 16ビットから8ビットに縮小しつつ下位と上位を統合• uint8x16_t u16x8_pixel_b;• NEONは命名規則がわかりやすく，複雑なIntel SSE/AVX よりもプログラミングしやすい

9. ストア2021 Susumu Yamazaki

35

©︎

OpenBLASに見る
SIMD処理プログラミングのヒント

2021 Susumu Yamazaki

36©︎OpenBLAS• https://github.com/xianyi/OpenBLAS• BLAS: 基本線形代数サブプログラム• たとえばGEMM(行列と行列の積)• データ型に合わせて最適化されている• OpenBLASは最適化されたBLASのライブラリでBSDライセンスで• SGEMM(単精度版)

公開されているもの• DGEMM(倍精度版)• 使い勝手よりスピードを追求してAPIが設計されている• CGEMM(複素数単精度版)• それでいて汎用性があるような絶妙さがある• ZGEMM(複素数倍精度版)• 次の計算を同時に実行することで，大域的に最適化できる• 行列の乗算• 行列の転置• 行列のスカラー倍• 行列の加算

BLAS (Basic Linear Algebra Subprograms) https://www.netlib.org/blas/

2021 Susumu Yamazaki

37

©︎

OpenBLAS• アセンブリコード例• https://github.com/xianyi/OpenBLAS/
blob/develop/kernel/arm64/
dgemm_kernel_4x4.S• https://github.com/xianyi/OpenBLAS/
blob/develop/kernel/arm64/
dgemm_kernel_4x8.S• https://github.com/xianyi/OpenBLAS/
blob/develop/kernel/arm64/
dgemm_kernel_8x4.S• 演算カーネルがCPUアーキテクチャやサイズで細かく場合分けされている• マクロ等を定義することで同じようなコード列を巧みに再利用している• 内側ループを展開して速度を向上させるようにしている

2021 Susumu Yamazaki

38

©︎

まとめ

2021 Susumu Yamazaki

39

©︎

まとめ
• SIMDは同じ動作をする計算ユニットに並列処理させる方式• 定石パターンをマクロ化すると良さそう• CPUのSIMD命令，ベクトル命令，GPUがSIMDに該当• CPUのSIMD命令は固定長のビット幅のSIMDレジスタで演算を行う• CPUのSIMD/ベクトルレジスタ数を超過しないようスケジューリングする• 変数に型を明記するとわかりやすい (ハンガリアン記法的なアプローチ)• SIMDレジスタのビット幅が変わると機械語命令の互換性がない• SIMD命令のビット幅を変える時にプログラミングやコンパイルをしなおす• NEONは命名規則がわかりやすく，複雑なIntel SSE/AVX よりもプログラミングしやすい必要がある• ベクトル命令ではベクトルレジスタ幅が変わっても，プログラミングや• 使い勝手よりスピードを追求し，それでいて汎用性があるような絶妙さがある感じでAPIを設計する

コンパイルをしなおす必要はない• C言語+Auto-vectorizationに比べてSIMD命令を手で記述して最適化を図ると2〜5倍の高速化効果がある• Auto-vectorizationを使う範囲ではAoS(構造体の配列)よりも• 演算カーネルをCPUアーキテクチャやサイズで細かく場合分けする• マクロ等を定義することで同じようなコード列を再利用する• 内側ループを展開して速度を向上させる

SoA(配列の構造体)のスタイルにメモリ配置した方が無難そう

2021 Susumu Yamazaki

40

©︎

置いた話を元に戻して2021 Susumu Yamazaki

41

©︎

たかだか2倍程度しか高速にならないのにIntrinsics / アセンブリコードのプログラミングをするの？🤔

2021 Susumu Yamazaki

42

©︎

手でSIMD命令を書くのは本当に性能が要求される部分に限定すべきである

2021 Susumu Yamazaki

43

©︎

どのくらい再利用されるかどのように再利用されるかを分析してAPIを設計すべきである

2021 Susumu Yamazaki

44

©︎

メンテナンスできる要員を常に確保し続ける覚悟が必要である

2021 Susumu Yamazaki

45

©︎

でもそんなことやってられないですよね？

2021 Susumu Yamazaki

46

©︎

そこで並列処理に長けたElixirのプログラムから超高速なSIMD命令＋マルチコア並列のコードを生成する技術シーズを育てています🎉

2021 Susumu Yamazaki

47

©︎

オンラインの研究会を開催しますので興味ある人は連絡ください
zacky1972@gmail.com

2021 Susumu Yamazaki48