行列コピー関数のテストプログラム作成|行列積高速化#20
この記事は、以下の記事を分割したものです。
[元の記事]行列積計算を高速化してみる
一括で読みたい場合は、元の記事をご覧ください。
行列積のテストプログラムは現在のものを使えば良いですが、行列A,Bのコピー関数は、行列積計算に比べ処理時間が短すぎて、現在のテストプログラムだと性能が向上したかどうかがほとんど分かりません。
そこで、コピー関数は独自にテストプログラムを用意することにします。行列積計算の場合と同様に、それぞれ下記の2つのプログラムを作成します。
(1)処理結果確認プログラム・・・正解値と処理結果の比較
(2)処理速度測定プログラム・・・処理時間を測定
20-1. 配列用エラーチェック関数
行列A Bのコピー関数の出力は、シリアライズしているので、行列ではなく一直線な線形配列になっています。行列型のエラーチェック関数は作成済みですが、配列型は作成していません。そこで、配列用のエラーチェック関数を作成しておきます。
作成方法としては、行列用エラーチェック関数を1次元に変更するだけです。シリアライズによって、連続メモリアクセスを前提にして良いので、ストライドの幅も引数から除外してしまいます。
実装は、次のようになります。
int check_array( const size_t n, const double *a, const double *b )
{
for( size_t j=0; j<n; j++ ){
double c1 = fabs(a[j]);
double c2 = fabs(b[j]);
double cmax = ( c1>c2 ? c1 : c2 );
double cdiff = fabs(c1-c2);
double eps = DBL_EPSILON * cmax;
if( cdiff > eps ){
fprintf(stderr,"[ERROR] An element a(%d) is invalid : t1=%G, t2=%G, diff=%G, eps=%G\n",j,a[j],b[j],cdiff,eps);
return 1;
}
}
return 0;
}
コピー処理は、総和計算を含まないため、丸め誤差も発生しません。そのため、行列型のエラーチェック関数の判定条件にあった、√Kの補正も行っていません。
20-2. テスト用構造体
行列積のテストプログラムと同様に、テストに必要な関数と引数をまとめた構造体を用意します。
typedef void(copy2d_func_t)(const double *A, size_t ldc, double *A2, const block2d_info_t* info );
typedef struct _copy2d_test_t {
copy2d_func_t * func;
const double *A;
size_t lda;
double *buf;
block2d_info_t* info;
} copy2d_test_t;
コピー関数を保持できるように、コピー関数の関数ポインタ型copy2d_func_tを定義しています。また、行列のサイズ情報はblock2d_info_t構造体に詰め込んでいるので、そのまま再利用しています。
20-3. 処理結果確認プログラム
処理結果の正しさは、現在のコピー関数及びスケール関数の実装と同じ結果が得られるかどうかで判定します。正解処理としては、関数名を変えて現在のコピー関数とスケール関数を使用します。
#define MAX_SIZE 1023
#define TILE_M 32
#define TILE_N 32
#define SEED 13892393
static void do_copy2d( copy2d_test_t *test ){
test->func( test->A, test->lda, test->buf, test->info );
}
static int copy2d_check_error( const copy2d_test_t *test1, const copy2d_test_t *test2 ){
return check_array( test1->info->M2*test1->info->N2, test1->buf, test2->buf );
}
int main( int argc, char** argv ){
block2d_info_t sizes={0,0,MAX_SIZE,MAX_SIZE,TILE_M,TILE_N};
copy2d_test_t test1 ={myblas_basic_copy_n,NULL,MAX_SIZE,NULL,&sizes};
copy2d_test_t test2 ={myblas_dgemm_copy_n,NULL,MAX_SIZE,NULL,&sizes};
opterr = 0;
int c;
while((c=getopt(argc,argv,"t")) != -1 ){
if( c == 't' ){
test1.func = myblas_basic_copy_t;
test2.func = myblas_dgemm_copy_t;
}else{
printf("Unknown option : %c\n",c);
return -1;
}
}
double* A = calloc(MAX_SIZE*MAX_SIZE,sizeof(double));
double* A1 = calloc(MAX_SIZE*MAX_SIZE,sizeof(double));
double* A2 = calloc(MAX_SIZE*MAX_SIZE,sizeof(double));
rand_matrix(MAX_SIZE,MAX_SIZE,A,MAX_SIZE,1,SEED);
test1.A = A;
test1.lda = MAX_SIZE;
test1.buf = A1;
test2.A = A;
test2.lda = MAX_SIZE;
test2.buf = A2;
do_copy2d( &test1 );
do_copy2d( &test2 );
int error = copy2d_check_error( &test1, &test2 );
if( error ){ printf("NG\n"); }else{ printf("OK\n"); }
free(A);
free(A1);
free(A2);
return error;
}
まず、簡便性のために、テスト構造体を受け取って、実際にテストする関数を呼び出す関数do_copy2d()を、ソースコード内部の関数として定義しています。同様に、エラーチェック関数を呼び出す関数copy2d_check_error()も定義しています。
main関数では、正解処理用のテスト構造体test1とチューニング用のtest2を用意しています。正解処理の関数は、現在のコードをコピーし、myblas_basic_copy_nという関数名で別途保存しています。正解関数は、今後一切書き換えません。
転置行列のコピー処理もテストプログラムは全く同じものになるので、実行オプション"-t"で切り替えられるように、実行時オプション処理を書いています。処理方法としては、"-t"が与えられたら転置用のコピー関数に差し替えているだけです。
判定の結果は、問題なければ「OK」、問題があれば「NG」と表示させています。
20-4. 処理速度測定プログラム
コピー関数とスケール関数は、どちらも演算よりもメモリアクセスで律速するため、理論性能は分かりません。正しくは、メモリバンド幅で見積もれるかもしれませんが、キャッシュメモリの階層構造があるため見積もりが難しいです。そこで、理論性能比は諦め、処理性能はB/s(Bytes per second)で測定することにします。
#define MAX_SIZE 2048
#define TILE_M 32
#define TILE_N 32
static void do_copy2d( copy2d_test_t *test ){
test->func( test->A, test->lda, test->buf, test->info );
}
int main( int argc, char** argv ){
block2d_info_t sizes={0,0,MAX_SIZE,MAX_SIZE,TILE_M,TILE_N};
copy2d_test_t test ={myblas_dgemm_copy_n,NULL,MAX_SIZE,NULL,&sizes};
opterr = 0;
int c;
while((c=getopt(argc,argv,"t")) != -1 ){
if( c == 't' ){
test.func = myblas_dgemm_copy_t;
}else{
printf("Unknown option : %c\n",c);
return -1;
}
}
double beta = 1.3923842e0;
double* A = calloc(MAX_SIZE*MAX_SIZE,sizeof(double));
double* A2= calloc(MAX_SIZE*MAX_SIZE,sizeof(double));
test.A = A;
test.lda = MAX_SIZE;
test.buf = A2;
int error = 0;
printf("size , elapsed time[s], copy size[KB], MB/s \n");
for( size_t n=16; n <= MAX_SIZE; n*=2 ){
test.info->M2 = n;
test.info->N2 = n;
init_matrix(test.info->M2, test.info->N2, A, test.lda, 1, 1e0 );
double t1 = get_realtime();
do_copy2d( &test );
double t2 = get_realtime();
double dt = t2 - t1;
double bytes = test.info->M2 * test.info->N2 * sizeof(double);
printf("%6u, %15G, %15G, %15G \n",n,dt,bytes/1024,bytes/dt/(1024*1024));
}
free(A);
free(A2);
return error;
}
行列積計算のテストプログラムと同様に、一辺のサイズ16~2048の行列で測定しています。
このプログラムの実行結果は、次のようになります。
size , elapsed time[s], copy size[KB], MB/s
16, 4.05312E-06, 2, 481.882
32, 1.90735E-06, 8, 4096
64, 3.00407E-05, 32, 1040.25
128, 5.29289E-05, 128, 2361.66
256, 0.000222921, 512, 2242.94
512, 0.000862837, 2048, 2317.94
1024, 0.00414395, 8192, 1930.52
2048, 0.0232768, 32768, 1374.76
だいたい、512×512の行列で、2.3GB/sほど出るようです。
次の記事
元の記事はこちらです。
ソースコードはGitHubで公開しています。
この記事が気に入ったらサポートをしてみませんか?