Upload
dc1394
View
248
Download
1
Embed Size (px)
Citation preview
C++による数値解析の並列化手法
@dc1394
福井技術者の集いその6発表資料スライド
自己紹介
Twitter: @dc1394
C++, C#, F#そしてRubyが好きです。
趣味はプログラミングではなく数値解析。
C++で量子力学の数値計算とかやってます。
概要
なぜC++で数値解析と並列化のコードを書くのか
並列化の分類について
タスク並列化のパフォーマンス比較・検証
データ並列化(GPGPU)のパフォーマンス比較・検証
なぜC++で数値解析のコードを書くのか
多数あるプログラム言語の中で、高性能なコンパイラ(Intel, PGIのコンパイラ等)及び、スーパーコンピュータ向けのコンパイラ(IBM, Cray, Fujitsuのコンパイラ等)でコンパイルできる言語は、Fortran, C及びC++だけである(ただし最近ではPythonも…)。
これらの言語の最新規格を列挙すると、 Fortran 2008 (2010年9月)
C11 (2011年12月)
C++14 (2014年12月)
これらの言語の中で、最もモダンな言語はC++である。
C++で数値解析と並列化のコードを書くときのメリットとデメリット
メリット FortranやC向けの、高性能かつ豊富なライブラリが使える(LAPACKやBLAS, GSL (GNU Scitific Library), FFTWなど)。
インラインアセンブラやintrinsic関数が使える(SIMDでデータ並列化する際に重要、後述)。
デメリット FortranやC向けのライブラリを使う際、C++らしく書けない。
言語レベルで並列処理をサポートしていない。
並列化の分類
命令レベル並列
並列パイプライン、スーパースカラ、アウト・オブ・オーダー実行, etc. →CPUとコンパイラの仕事
データ並列
Single Instruction Multiple Data (SIMD)
General-purpose computing on graphics processing
units (GPGPU)
タスク並列
スレッド並列
プロセス並列
並列化の分類
データ並列
SIMD: インラインアセンブラ, SIMD intrinsic,
Boost.SIMD(開発中), OpenMP 4.0, Cilk Plus, etc.
GPGPU: CUDA (Thrust), OpenCL (Boost.Compute),
C++ AMP, DirectCompute, OpenMP 4.0, etc.
タスク並列
スレッド並列: OpenMP, Intel® Threading Building
Blocks (Intel® TBB), Cilk Plus, Parallel Patterns Library
(Microsoft PPL)
プロセス並列: Message Passing Interface (MPI)
(Boost.MPI)
スレッド並列とプロセス並列
タスク並列
スレッド並列とプロセス並列
スレッド並列:1つのプロセスの中でスレッドを複数個生成させ、各スレッドに異なるCPUコアを割り
当てて並列処理を行う方法(共有メモリ型並列計算機専用)。
プロセス並列:複数のプロセスを起動して、各プロセスに異なるCPUコアを割り当てて並列処理を
行う方法(分散メモリ型並列計算機向け、共有メモリ型並列計算機でも動作)。
ハイブリッド並列
スーパーコンピュータでは、プロセス並列(MPI)と、スレッド並列(OpenMP)の両方を駆使したハイブリッド並列化の利用が普通である。
ノード間をMPIでプロセス並列化し、複数のCPUとCPUコアがあるノード内をOpenMPでスレッド並列化するケースが一般的である。
参考スライド
C++における並列化については、yohhey様が書かれた以下のスライドが非常に参考になります。
「新しい並列for文のご提案」( http://www.slideshare.net/yohhoy/boostjp14-
parfor-20140301-31781446 )
並列化の一般論
1. まず最初に、シリアルコード(並列化しないコード)で、バグのないプログラムを作成する(最初からパラレルコード(並列化コード)を書こうとするのは無謀である)。
2. シリアルコードでプログラムの無駄をなくし、チューニングする。
3. プロファイラ等で、プログラムの各計算部分の実行時間の測定を行い、ホットスポットを見つける。
並列化の一般論
4. ホットスポットの部分で、かつ並列化できる部分を並列化する。ただし、プログラムの改変は一度に一部分ごとに行い、その都度シリアルコードと結果を比較する。パラレルコードのデバッグは、シリアルコードと比較し、難しい。従って、ステップバイステップを心がける。
5. パラレルコードが完成したと思ったら、並列数や問題サイズを変えて、結果を慎重にチェックする。
6. 並列数に対する計算時間を測定し、並列効率比をプロットする。
数値積分の並列化
以下の定積分を、Simpsonの公式によって数値積分する。
この定積分は、解析的に積分した結果が厳密に1となるため、数値積分の結果と容易に比較できる。
この数値積分のプログラムを、C++11非同期処理, OpenMP, TBB, Cilk Plus, PPL及びMPIによって並列化し、シリアルコードと比較した場合の並列効率比や誤差を調べる。
Simpsonの公式とは
関数f(x)を、微小区間[xj, xj+2]において、2次関数P(x)で近似する(上図参照)。
このとき、
である(ただし、xj+2 - xj+1 = xj+1 - xj = Δh)
左図は、http://www.yamamo10.jp/y
amamoto/lecture/2007/5E_
comp_app/test_4/html/nod
e2.html
より引用
ΔhΔh
Simpsonの公式とは
ここで、区間[a, b]にわたっての関数f(x)の積分は、上式を足し合わせればよい。ただし、 j = 0, 2,
4,…, N-2と足し合わせる。
ΔhΔh
並列化無しのコード
C++11非同期処理の特徴、メリット、デメリット
特徴:C++11標準の機能。
メリット
移植性に比較的優れる(C++11対応のコンパイラがあればOK)。
デメリット
各スレッドに対する計算範囲を、自分でコーディングする必要がある。
コードが複雑になり、可読性が落ちる。
C++11非同期処理で並列化したコード
OpenMPの特徴、メリット、デメリット
特徴:並列コンピューティング用のFortran, C/C++言語拡張。
メリット最も移植性に優れる。
比較的可読性に優れる。
デメリット C++例外の扱いが面倒(OpenMPスレッドからC++例外が投げられた際、例外がcatchされず、実行時クラッシュなどが起こる)。
上記の問題に対処しようとすると、コードが複雑になり、可読性が低くなる。
OpenMPで並列化したコード
TBBの特徴、メリット、デメリット
特徴:Intelが公開している、マルチスレッド対応のC++テンプレートライブラリ。ICCに付属のものとオープンソース版とがある。
メリット
移植性に優れる(GCC, Clang, MSVC, ICC,…等の普通のコンパイラ上なら動く)。
デメリット
若干コードが複雑になり、可読性が落ちる。
TBBで並列化したコードその1
TBBで並列化したコードその2
Cilk Plusの特徴、メリット、デメリット
特徴:Intelによる並列コンピューティング用のC/C++言語拡張。
メリット
可読性に優れる。
デメリット
対応しているコンパイラが少ない(ICC及びGCC 5.1以上のみ)。
Clangは、Cilk Plus/LLVMというプロジェクトで対応。
Cilk Plusで並列化したコード
PPLの特徴、メリット、デメリット
特徴:Microsoft製の、並列プログラミングをサポートするライブラリ。
メリット
比較的可読性に優れる。
デメリット
対応しているコンパイラがMSVC(VC10以降)のみ。
他の方法と比べて、誤差が大きく、またパフォーマンスが低い(後述)。
PPLで並列化したコード
MPIの特徴、メリット、デメリット
特徴:Fortran, C/C++で、並列コンピューティングを利用するための標準化された規格である。
メリット分散メモリ型並列計算機においては、この方法が必須。
移植性に優れる。
デメリット
各プロセスに対する計算範囲を、自分でコーディングする必要がある。
コードが極めて複雑になり、可読性が大きく落ちる。
並列化数を、実行時にコマンドラインオプションから明示的に指定する必要がある。
デバッグが極めて困難。
MPIで並列化したコード
MPIで並列化したコード(続き)
タスク並列のパフォーマンスの検証
それぞれの方法において、並列化のパフォーマンスを測定し、結果を次ページの表1にまとめた(区間[1, 4]を10億個の区間に分割、ループ5億回)。
計測環境:
CPU: Intel Core i7-3930K (Sandy Bridge-E, Hyper Threading ON (6C12T), SpeedStep OFF, Turbo Boost OFF)
コンパイラ: Intel® Parallel Studio XE 2016 for C++ on Visual C++ 2015 (x64ビルド)
MPIの実装: Microsoft MPI v7
OS: Microsoft Windows 8.1 (64bit)
タスク並列のパフォーマンスの検証
計算時間(ミリ秒)
並列効率比(倍)
積分結果 並列化なしに対する誤差
誤差の標準偏差
並列化なし
1019.3 1 0.999999999999949 0 0
C++11
非同期172.9 5.90 0.999999999999999 5.0E-14 0
OpenMP 180.1 5.66 0.999999999999999 5.0E-14 0
TBB1 172.2 5.92 0.999999999999998 4.9E-14 2.2E-15
TBB2 170.0 6.00 1.000000000000000 5.1E-14 0
Cilk Plus 178.4 5.71 1.000000000000000 5.1E-14 4.7E-16
PPL 397.2 2.57 1.000000000000020 7.1E-14 9.4E-15
MPI 171.1 5.96 0.999999999999999 5.0E-14 0
表1 各方法で並列化した場合のパフォーマンスの比較(5回の平均)
結局、どれを使うべきか
計算機が普通のPCなら、TBBかCilk Plusを使うことを推奨する。
あくまで移植性を重視するなら、OpenMPが唯一の選択肢となる。
個人でPCクラスタを組んでいたり、スーパーコンピュータへの移植を考えるなら、MPI単独か、OpenMPとMPIのハイブリッド並列が選択肢になる。
個人的な疑問:TBBやCilk Plusと、MPIのハイブリッド並列は可能なのだろうか?
ソースコードへのリンク
これらのプログラム(simpson, simpsonmpi)のソースコードは、GitHub上で公開しています。
https://github.com/dc1394/simpson
https://github.com/dc1394/simpsonmpi
ライセンスは2条項BSDライセンスとします。
SIMDとGPGPU
データ並列
SIMDとは
SIMD(Single Instruction Multiple Dataの略)とは、コ
ンピュータで並列処理を行なうための設計様式の一つで、一つの命令を同時に複数のデータに適用し、並列に処理する方式。
cf. フリンの分類, SISD, MISD, MIMD
そのような処理方式をベクトル演算、ベクトル処理などと呼ぶことがあり、この方式による並列化のことをベクトル化などという。
ベクトル化の注意
2016年現在のコンパイラは非常に優秀であり、単にコンパイラオプションでSSE/AVX命令を使うように指示するだけで、コンパイラが自動的にベクトル化を行ってくれる。
インラインアセンブラ, SIMD intrinsic, Boost.SIMDなどによる手動のベクトル化は、コンパイラの自動ベクトル化に、パフォーマンスで及ばない場合があり、注意が必要。
コンパイラを「負かせる」には、極めて慎重なプログラミングが必要である。
SSE2命令(SIMD intrinsic)を使ってmemcpyを自作したコード
GPGPUとは
GPGPU(General-purpose computing on graphics processing unitsの略)とは、GPUの演算資源を画像処理以外の目的に応用する技術のこと(なお、GPGPUはSIMDの一つに分類される)。
GPUはもともと画像処理を専門とする演算装置であり、非常に多数のデータを並列処理することができる。従ってCPUと比較すると、GPUは並列的な演算が得意である。
そのため、プログラムの並列演算部分をGPUで計算させることで、プログラムの高速化が望める。
CUDAとは
CUDA(Compute Unified Device Architectureの略)とは、NVIDIAが提供するGPU向けのC/C++言語の統合開発環境であり、コンパイラ(nvcc)やライブラリなどから構成されている。
CUDAはGPGPU技術の先駆けである(CUDA 1.0の提供開始は2007年7月)。
このため、先発技術であるCUDAは、教育・研究機
関での採用事例が多いほか、機械学習などの分野で産業界でも採用への取り組みが進んでいる。
CUDAのメリット、デメリット
メリット
CUDA 7.0から、C++11規格のサポートが強化され、デバイスコード(CPUで動かすコード)におけるラムダ式の利用などが可能となっている。
Thrustと呼ばれる、(C++のSTLによく似た)GPU用の並列アルゴリズムライブラリが提供されている。
カーネルコード(GPUで動かすコード)が普通にコンパイルできる。
デメリット
NVIDIAのGPU専用である。
OpenCLとは
OpenCL(Open Computing Languageの略)は、OpenCL C言語による、マルチコアCPUやGPUなど
による異種混在の計算資源を利用した並列コンピューティングのためのクロスプラットフォームなフレームワークである。
マルチコアCPUやGPU以外にも、FPGAやXeon Phi
などをサポートしている。
OpenCLのメリット、デメリット
メリット少なくとも、NVIDIA, AMD及びIntelのGPUで動作する。
デバイスコードでは、C++11が使用可能。
Boost.Compute(Boost 1.61.0以降に含まれる)と呼ばれる、OpenCLのC++ラッパーが存在し、カーネルコードを簡潔に記述できる。
デメリット
オンラインコンパイルでは、カーネルコードが実行時にロードされコンパイルされるため、デバッグが困難になり、初回起動時に時間がかかる(Boost.Computeはこちらしか対応していない)。
オフラインコンパイル(通常のコンパイル)では移植性が落ちる。
分子動力学のGPGPUによる並列化
シンプソンの公式による数値積分を、GPGPUで並列化すると、CPUより遅かったという現象に直面した(調査中)ため、簡単な分子動力学(Molecular
Dynamics, MD)のコードをGPGPUで並列化することにした。
分子動力学については、拙著のスライド(以下URL)を参照のこと。
http://www.slideshare.net/dc1394/4-52829134
GPGPUのパフォーマンス検証
LJポテンシャルの下で相互作用する、2048個のアルゴン原子を100ステップ計算した場合の、並列化無し、CPUをTBB(6コア12スレッド)で並列化した場合、CUDA及びOpenCLで並列化した場合の計算時間を測定し、結果を次ページの表1にまとめた。
計測環境:
GPU: NVIDIA GeForce GTX 970 (Maxwell)
GeForce Driverバージョン: 368.69
CUDAコンパイラ: nvcc 7.5.17
OpenCLバージョン: OpenCL 1.2 CUDA
OpenCLのCPU側のコンパイラや、OS, CPU等については、タスク並列のパフォーマンス比較の記載と同じ。
GPGPUのパフォーマンス検証
CUDA及びOpenCLで並列化した場合は、並列化無しの場合に対してそれぞれ29.1倍、28.3倍程度速く、CPUをTBB(6コア12スレッド)で並列化した場合に対しても、それぞれ6.2倍、6倍程度速い。
CUDAとOpenCLでは、CUDAの方が若干速かった。
計算時間(秒) 並列効率比(倍) TBBに対する並列効率比(倍)
並列化無し 1307.9 1 0.21
TBB 279.0 4.69 1
CUDA 45.0 29.06 6.20
OpenCL 46.2 28.31 6.04
表1 GPGPUのパフォーマンスの比較(5回の平均)
GPGPUの問題
デバッグが困難。
基本的に、GPUで倍精度演算を行うためには、高価なGPGPU専用カードが必要(e.g. NVIDIA Tesla,
AMD FirePro, Intel Xeon Phi)。
デバイス(CPU)とホスト(GPU)間のデータ転送が
頻繁に行われると、それがボトルネックになり得るので、慎重にプログラミングする必要がある。
ソースコードへのリンク
これらのプログラム(LJ_Argon_MD_CUDA,
LJ_Argon_MD_OpenCL)のソースコードは、GitHub
上で公開しています。
https://github.com/dc1394/LJ_Argon_MD_CUDA
https://github.com/dc1394/LJ_Argon_MD_OpenC
L
ライセンスは2条項BSDライセンスとします。
まとめ
並列化の分類について説明した。
タスク並列について、種々の方法について説明し、それらの方法のパフォーマンスを比較・検証した。
手動ベクトル化の注意点について説明した。
GPGPUについて、CUDAとOpenCLについて説明し、これらのパフォーマンスを比較・検証した。