Cell を用いて行列の積演算を考えているが、まずは通常のC言語でのプログラムを作成し、どのように演算させれば効率的であるかを調べてみた。
2次元配列で用意された行列ではそのまま演算してしまうと、データのアドレスが飛び飛びになってしまい、それがボトルネックとなってしまう。また、SPE へ割り振る際にも連続していないと1要素ずつ DMA 転送することになってしまい、効率が悪い。
上記の2つの理由から、そのまま演算させるよりも、AxBであれば、AxtBとして、行列Bを転置させることがその2つの観点からも都合が良いようである。都合が良いといっても、転置する際にかかる処理量がどれほど影響するかも考慮しなければならないため、次のような条件において実験してみた。
■実験環境■
・PenM
OS : Vine 4.1 (on VMware)
CPU : Pentium M 1.66MHz
Mem : 512MB
・PS3
OS : Fedora Core 6
CPU : Cell Broadband Engine 3.2GHz
Mem : 256MB
入力データ:
double 型の2次元配列としてデータを格納している。サイズは 512x512。
ex) in_A[BUFSIZE][BUFSIZE], in_B[BUFSIZE][BUFSIZE]
出力データ:
double 型の2次元配列としてデータを格納している。サイズは 512x512。
ex) out[BUFSIZE][BUFSIZE]
■実験1■
まずは何も工夫をせずに最も簡単なタイプなもので測定してみた。サイズは BUFSIEZ = 512 というあまり大きななものではないが、予想以上に実行時間がかかってしまった。下記のように 512 x 512 x 512 回演算することになるため、データが飛び飛びであることが影響しているのであろうか。ここでの PS3 は PPE のみを使用しているので、Pentium M と比べて倍ほどかかってしまっている。
for (i = 0; i < BUFSIZE; i++) {
for (j = 0; j < BUFSIZE; j++) {
for (k = 0; k < BUFSIZE; k++) {
out[i][j] = out[i][j] + in_A[i][k] * in_B[k][j];
}
}
}
PenM: 8.5 8.3 8.5 9.0 8.8 [s]
PS3 : 17.4 18.2 17.4 17.5 18.0 [s]
■実験2■
AxBという演算を、AxtB(Aに転置したBをかける)として演算する。事前に行列Bを転置してしまうことで、連続した演算になるため、高速化が期待できる。PenM では 1/5 、PS3 においては 1/10 とかなり改善させている。転置に要した時間も全体から見てもかなり小さい。今回は安直に tmp_in_B[][] という2次元配列を用意してしまったが、メモリを無駄遣いしないためにも、入れ換えをすることで転置にするようにするのもよいのかもしれない。
for (i = 0; i < BUFSIZE; i++) {
for (j = 0; j < BUFSIZE; j++) {
tmp_in_B[i][j] = in_B[j][i];
}
}
for (i = 0; i < BUFSIZE; i++) {
for (j = 0; j < BUFSIZE; j++) {
for (k = 0; k < BUFSIZE; k++) {
out[i][j] = out[i][j] + in_A[i][k] * tmp_in_B[j][k];
}
}
}
PenM:
積演算に要した時間 1.5 1.4 1.5 1.4 1.4 [s]
そのうち転置に要した時間 20[ms] 程度
PS3:
積演算に要した時間 1.6 1.6 1.6 1.6 1.6 [s]
そのうち転置に要した時間 300[ms] 程度
2次元配列で用意された行列ではそのまま演算してしまうと、データのアドレスが飛び飛びになってしまい、それがボトルネックとなってしまう。また、SPE へ割り振る際にも連続していないと1要素ずつ DMA 転送することになってしまい、効率が悪い。
上記の2つの理由から、そのまま演算させるよりも、AxBであれば、AxtBとして、行列Bを転置させることがその2つの観点からも都合が良いようである。都合が良いといっても、転置する際にかかる処理量がどれほど影響するかも考慮しなければならないため、次のような条件において実験してみた。
■実験環境■
・PenM
OS : Vine 4.1 (on VMware)
CPU : Pentium M 1.66MHz
Mem : 512MB
・PS3
OS : Fedora Core 6
CPU : Cell Broadband Engine 3.2GHz
Mem : 256MB
入力データ:
double 型の2次元配列としてデータを格納している。サイズは 512x512。
ex) in_A[BUFSIZE][BUFSIZE], in_B[BUFSIZE][BUFSIZE]
出力データ:
double 型の2次元配列としてデータを格納している。サイズは 512x512。
ex) out[BUFSIZE][BUFSIZE]
■実験1■
まずは何も工夫をせずに最も簡単なタイプなもので測定してみた。サイズは BUFSIEZ = 512 というあまり大きななものではないが、予想以上に実行時間がかかってしまった。下記のように 512 x 512 x 512 回演算することになるため、データが飛び飛びであることが影響しているのであろうか。ここでの PS3 は PPE のみを使用しているので、Pentium M と比べて倍ほどかかってしまっている。
for (i = 0; i < BUFSIZE; i++) {
for (j = 0; j < BUFSIZE; j++) {
for (k = 0; k < BUFSIZE; k++) {
out[i][j] = out[i][j] + in_A[i][k] * in_B[k][j];
}
}
}
PenM: 8.5 8.3 8.5 9.0 8.8 [s]
PS3 : 17.4 18.2 17.4 17.5 18.0 [s]
■実験2■
AxBという演算を、AxtB(Aに転置したBをかける)として演算する。事前に行列Bを転置してしまうことで、連続した演算になるため、高速化が期待できる。PenM では 1/5 、PS3 においては 1/10 とかなり改善させている。転置に要した時間も全体から見てもかなり小さい。今回は安直に tmp_in_B[][] という2次元配列を用意してしまったが、メモリを無駄遣いしないためにも、入れ換えをすることで転置にするようにするのもよいのかもしれない。
for (i = 0; i < BUFSIZE; i++) {
for (j = 0; j < BUFSIZE; j++) {
tmp_in_B[i][j] = in_B[j][i];
}
}
for (i = 0; i < BUFSIZE; i++) {
for (j = 0; j < BUFSIZE; j++) {
for (k = 0; k < BUFSIZE; k++) {
out[i][j] = out[i][j] + in_A[i][k] * tmp_in_B[j][k];
}
}
}
PenM:
積演算に要した時間 1.5 1.4 1.5 1.4 1.4 [s]
そのうち転置に要した時間 20[ms] 程度
PS3:
積演算に要した時間 1.6 1.6 1.6 1.6 1.6 [s]
そのうち転置に要した時間 300[ms] 程度