こんにちは
この投稿は、このトピックに関する
最初の投稿の続きです。
この投稿は、Tomsk Polytechnic University Conferenceで私が発表する記事「Java for High Performance Computing」からの抜粋です。
ベクトルのスカラー積は、ベクトルの対応する要素のすべての積の合計です。
この問題を解決するために、C(私ではなく:-)とJavaの2つのプログラムが作成されました。
両方のプログラムは、Tomsk Polytechnic Universityにインストールされ、それぞれLinux SuSE Enterpriseバージョン10.3を実行する2つのIntel Xeon 5150 2.66 Ghzプロセッサーと8 GBのRAMを備えた24ノードで構成されるSKIF-Polytechスーパーコンピュータークラスターでテストされました。
最初のケースでは、ランダムに初期化された次元99999999整数要素の2つのベクトル、そして2番目のケースでは、99999999実要素の2つのベクトルがデータセットとして使用されました。 両方のプログラムは、使用されるプロセッサコアの数(2から40)の変更により、各データセットに対して21回、それぞれ2回ずつ起動されました。
両方のプログラムが次のことに注意する必要があります。
*最適化されていません。
*同じ機能を使用します(言語の内部機能を除く)。
したがって、コメントを追加することを強くお勧めします。
問題を解決するために必要な理論
CおよびJavaのMPI実装では、最初は混乱する可能性のある2つの違いがあります。
1)メッセージ転送機能では、Cの最初の引数はオブジェクトであり、Javaでは1次元配列です。
2)引数の異なるシーケンス。
ベクトルのスカラー積を計算するには、次の問題を解決する必要があります。
1)それぞれN要素の2つのベクトルを作成し、値を初期化します。
2)ノードに送信される粒子にベクトルを分割します。
3)パーティクルを送信します。
4)ノードで粒子を取ります。
5)計算を行います。
6)送り返す。
7)結果を要約して取得します。
8)プログラムの完了にかかる時間を計算します。
ポイントについて:
1)それぞれN要素の2つのベクトルを作成し、値を初期化します
Cの場合、対応するメモリを配列に割り当てる必要があります-malloc(n * sizeof(double))およびランダムrand()ループで値を初期化します。 Javaの場合、Randomクラスのオブジェクトであるベクトル配列を作成するだけで十分です(オブジェクトの作成には時間がかかることに注意してください)。このオブジェクトを使用して、ベクトル配列を初期化します。
2)ノードに送信される粒子にベクトルを分割します
CおよびJavaの場合、同じ方法で解決されます。
n =合計/ numprocs + 1、ここで
Nはノードあたりのパーティクルの数です。
合計-ベクトルの長さ、
numprocs-プール内のプロセスの数(MPI_COMM_Size)。
3)パーティクルを送信します。
MPIライブラリの関数MPI_Bcastを使用します。これは、プール内のすべてのプロセスにオブジェクトを送信します。 仕様については、
メーカーのウェブサイトにお問い合わせください。
その結果、Javaで配列を送信すると次のようになります。
MPI.COMM_WORLD.Bcast(d, 1, 0,MPI.DOUBLE, 0);
MPI.COMM_WORLD.Send(a,0,a.length,MPI.DOUBLE,dest,0);
MPI.COMM_WORLD.Send(b,0,b.length,MPI.DOUBLE,dest,0);
ここで、dは配列からの断片の長さ、
aは最初のベクトルです
bは2番目のベクトルです。
4)ノードでパーティクルを受け入れる
MPI.COMM_WORLD.Recv(a,0,d[0],MPI.DOUBLE,0,0);
MPI.COMM_WORLD.Recv(b,0,d[0],MPI.DOUBLE,0,0);
コメントはありません。
5)計算する
for (int i=0; i<d[0];i++){
sum[0]+=a[i]*b[i];
}
6)送り返す。 7)結果を要約して取得します。
そして興味深い点があります-2つのタスクを1つに結合します。 私たちに必要なすべてのアクションを実行する縮小関数を使用します-結果を収集し、それらを1次元配列に入れます(Javaの実装には単純な変数があってはならないことを忘れないでください!)。
MPI.COMM_WORLD.Reduce(sum,0,result,0,1,MPI.DOUBLE,MPI.SUM,0);
8)プログラムを完了するのにかかる時間を計算する
これには2つの組み込み関数が使用されますが、どちらも標準関数のラッパーであるMPI.Wtime(壁時間)です。 プログラムの最初に最初のものを呼び出し、最後にプログラムの合計実行時間(計算ではなく!)を計算します。
結論
Javaのすべての欠点とCプログラムとJavaプログラムの実行時間の大きな違いにもかかわらず、プログラミング言語の選択に関する最終決定は、主題領域と研究者グループが自分自身を見つけた状況を徹底的に分析した後にのみ行うことができます。 場合によっては、Cの使用は生産性の向上と鉄への集中(したがってプロセス全体の最適化)により正当化されますが、Cの使用はプログラマーに大きな責任を負わせます。 -制御下で、プログラムがプログラム全体を「リーク」してドラッグする重大なケースの発生を防止します。 これは、真剣な研究において非常に重要なポイントです。
一方、Javaの使用も正当化されます。 パフォーマンスの低下、浮動小数点数の計算などの問題にもかかわらず、Javaには、仮想マシンで状況を監視する、例外的な状況をキャッチするツールを開発する、「数値クラッシャー」を開発するための低入力しきい値、および次のような複雑で曖昧なツールがないなどの利点がありますポインタまたは手動のメモリ割り当て-これらはすべて、研究チーム向けの並列プログラムを開発するためのプログラミング言語としてJavaを選択するための十分な議論かもしれません。 C.に有能なプログラマで構成されていないアトリエ、
Cプログラム
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <signal.h>
#define MYTAG 1
int myid, j;
char processor_name[MPI_MAX_PROCESSOR_NAME];
double startwtime = 0.0, endwtime;
int main(int argc,char *argv[])
{
int total, n, numprocs, i, dest;
double *a, *b, sum, result;
int namelen;
MPI_Status status;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
MPI_Get_processor_name(processor_name,&namelen);
if (myid == 0) {
total = atoi(argv[1]);
}
printf("Process %d of %d is on %s\n",
myid, numprocs, processor_name);
startwtime = MPI_Wtime();
n = total / numprocs + 1;
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
a = malloc(n*sizeof(double));
b = malloc(n*sizeof(double));
if ((a == NULL) || (b == NULL)) {
fprintf(stderr,"Error allocating vectors (not enough memory?)\n");
exit(1);
}
if (myid == 0) {
for (dest=1; dest < numprocs; dest++) {
for (i=0; i < n; i++) {
a[i] = 4294967296;//rand();
b[i] = 4294967296;//rand();
}
MPI_Send(a, n, MPI_INT, dest, MYTAG, MPI_COMM_WORLD);
MPI_Send(b, n, MPI_INT, dest, MYTAG, MPI_COMM_WORLD);
}
n = total - n*(numprocs-1);
for (i=0; i < n; i++) {
a[i] = rand();
b[i] = rand();
}
} else {
MPI_Recv(a, n, MPI_INT, 0, MYTAG, MPI_COMM_WORLD, &status);
MPI_Recv(b, n, MPI_INT, 0, MYTAG, MPI_COMM_WORLD, &status);
}
printf("Process %d on node %s starting calc at %f sec\n",
myid, processor_name, MPI_Wtime()-startwtime);
sum = 0.0;
for (i=0; i<n; i++)
sum += a[i]*b[i];
printf("Process %d on node %s ending calc at %f sec\n",
myid, processor_name, MPI_Wtime()-startwtime);
MPI_Reduce(&sum, &result, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if (myid == 0) {
endwtime = MPI_Wtime();
printf("Answer is %f\n", result);
printf("wall clock time = %f\n", endwtime-startwtime);
fflush(stdout);
}
MPI_Finalize();
return 0;
}
Javaプログラム
import mpi.*;
import java.util.*;
public class scalar {
public static void main(String args[]){
MPI.Init(args);
double[] result = new double[1];
int me = MPI.COMM_WORLD.Rank();
int size = MPI.COMM_WORLD.Size();
double startwtime=0.0;
double endwtime=0.0;
int total = 99999999;
int[] d = new int[1];
d[0] = total/size+1;
double[] a = new double[d[0]];
double[] b = new double[d[0]];
Random r = new Random();
MPI.COMM_WORLD.Bcast(d, 1, 0,MPI.INT, 0);
if (me == 0){
startwtime = MPI.Wtime();
for (int dest=1; dest<size;dest++){
for (int i=0; i<d[0]; i++){
a[i] = r.nextDouble();
b[i] = r.nextDouble();
}
MPI.COMM_WORLD.Send(a,0,a.length,MPI.INT,dest,0);
MPI.COMM_WORLD.Send(b,0,b.length,MPI.INT,dest,0);
}
d[0] = total - d[0]*(size-1);
for (int i=0; i<d[0];i++){
a[i] = r.nextDouble();
b[i] = r.nextDouble();
}
} else {
MPI.COMM_WORLD.Recv(a,0,d[0],MPI.INT,0,0);
MPI.COMM_WORLD.Recv(b,0,d[0],MPI.INT,0,0);
}
int[] sum = new int[1];
for (int i=0; i<d[0];i++){
sum[0]+=a[i]*b[i];
}
MPI.COMM_WORLD.Reduce(sum,0,result,0,1,MPI.INT,MPI.SUM,0);
if (me == 0){
System.out.println("answer is"+result[0]+" time of calcs is equal to "+(MPI.Wtime()-startwtime));
}
MPI.Finalize();
}
}