C++ AMP (Accelerated Massive Parallelism)の試用
Visual C++ において、GPU(グラフィックスデバイス)などによる並列計算を行うC++ AMPが利用できるようになったので使ってみた。使用PCはPanasonicのLet's note CF-SX2である。
C++ AMPはVisual C++によるプログラミングの中で使え、画像処理のみならずデータ分析などのプログラミングにおいて処理の並列化を行うときの機能として有望である。データ分析プログラミングにおける並列化の効果を見るために、次の行列計算を行ってみた。
nvar個の変数のncase人のデータを表す型の行列に対して積を求める。この計算をAMPを用いる方法を2通りと、AMPを用いない方法1つを以下のように作成した。
#include
<iostream>
#include
<iomanip>
#include
<amp.h>
#include
<stdlib.h>
using
namespace std;
using
namespace concurrency;
LARGE_INTEGER
L_I;
double
f;
const
double c = pow(2.0, 32.0);
double
MyGetTime(){
QueryPerformanceCounter(&L_I);
double
v = L_I.HighPart * c + L_I.LowPart;
return
1000000.0 * v / f;
}
int
main(){
if (!QueryPerformanceFrequency(&L_I)){
cout << "QueryPerformanceFrequency
failed." << endl;
return
9;
}
f = L_I.LowPart + L_I.HighPart * c;
std::vector<accelerator> accs
= accelerator::get_all();
if (accs.size() > 0){
wcout << accs[0].description
<< endl;
wcout << accs[0].dedicated_memory << "(memory)" << endl;
wcout << "Display..." << (accs[0].has_display ?
"connected." : "not connected.") << endl;
accelerator::set_default(accs[0].device_path);
}
srand(1);
const int ncase
= 1000;
const int nvar
= 3;
cout << endl <<
"nvar = " << nvar
<< " ncase = " << ncase
<< endl;
//#define
my_real double
#define
my_real float
my_real ** X = new my_real*[nvar];
for (int i = 0; i
< nvar; i++){
X[i] = new my_real[ncase];
for
(int j = 0; j < ncase;
j++)
X[i][j] = (rand() *
2.0 / 32767.0) - 1.0;
}
my_real ** XPX = new my_real*[nvar];
for (int i = 0; i
< nvar; i++){
XPX[i] = new my_real[nvar];
XPX[i][nvar - 1] = 0.0;
}
my_real * Prod = new my_real[ncase];
my_real t0 = MyGetTime();
for (int i = 0; i
< nvar; i++)
for
(int j = 0; j <= i;
j++){
{
array_view<const my_real, 1> a(ncase, X[i]);
array_view<const my_real, 1> b(ncase, X[j]);
array_view<my_real, 1> c(ncase, Prod);
c.discard_data();
parallel_for_each(c.extent,
[=](index<1> k) restrict(amp) {
c[k]
= a[k] * b[k];
});
}
my_real sum = 0.0;
for
(int k = 0; k < ncase;
k++)
sum
+= Prod[k];
XPX[i][j] = XPX[j][i] = sum;
}
my_real t1 = MyGetTime();
cout << endl <<
"Elapsed time = " << (t1 - t0) << "micro sec"
<< endl;
for (int i = 0; i
< nvar; i++){
for
(int j = 0; j < nvar;
j++)
cout << setw(15) <<
XPX[i][j];
cout << endl;
}
my_real * XL = new my_real[nvar * ncase];
for (int i = 0; i
< nvar; i++)
for
(int j = 0; j < ncase;
j++)
XL[j
+ i * ncase] = X[i][j];
my_real * ProdL = new my_real[nvar * nvar];
t0 = MyGetTime();
{
array_view<const my_real, 2> aXL(nvar, ncase, XL);
array_view<my_real, 2> aProdL(nvar, nvar,
ProdL);
aProdL.discard_data();
parallel_for_each(aProdL.extent,
[=](index<2> idx) restrict(amp)
{
int h = idx[0];
int k = idx[1];
double
sum = 0.0;
for
(int s = 0; s < ncase;
s++)
sum
+= aXL(h, s) * aXL(k, s);
aProdL[idx] = sum;
});
}
for (int i = 0; i
< nvar; i++)
for
(int j = 0; j < nvar;
j++)
XPX[i][j] = XPX[j][i] = ProdL[j + i * nvar];
t1 = MyGetTime();
cout << endl <<
"Elapsed time(1) = " << (t1 - t0) << "micro
sec" << endl;
for (int i = 0; i
< nvar; i++){
for
(int j = 0; j < nvar;
j++)
cout << setw(15) <<
XPX[i][j];
cout << endl;
}
double
tt0 = MyGetTime();
for (int i = 0; i
< nvar; i++)
for
(int j = 0; j <= i;
j++){
my_real tv = 0.0;
for
(int k = 0; k < ncase;
k++)
tv += X[i][k] * X[j][k];
XPX[i][j] = XPX[j][i] = tv;
}
double
tt1 = MyGetTime();
cout << endl <<
"Elapsed time = " << (tt1 - tt0) << "micro sec"
<< endl;
for (int i = 0; i
< nvar; i++){
for
(int j = 0; j < nvar;
j++)
cout << setw(15) <<
XPX[i][j];
cout << endl;
}
char ck_c;
cin >> ck_c;
return
0;
}
上のプログラムを実行したときの画面を図1に示す。ただし、AMPによってGPUが用いられるようにソリューション構成を「Release」に設定してビルドしている。実行は「デバッグなしで開始(Ctrl + F5)」で行っている。
図1
AMPを用いた場合の計算時間は、いずれも0 microsecondであるが、AMPを用いない場合は16.7 microsecondsとなっている。これらの値は、そのときのPCの状態によって変わり得るが、図1の結果からAMPによる処理の並列化の効果が確認できる。