A64fx も情報がオープンにできるようになったので、特に差分法関連で少し調
べていること(途中経過ですが)等を。
コードの性能評価をしていてどうもよくわからないことがおきているので、非
常に単純な、L2キャッシュにはいるくらいの配列をコピーするのを繰り返す、
という評価プログラムを作ってみました。
諸般の事情で3ファイルで、こんな感じのものです。
本体:
// l2test.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "fj_tool/fapp.h"
#ifdef __ARM_FEATURE_SVE
#include <arm_sve.h>
#endif /* __ARM_FEATURE_SVE */
void svcopy(svfloat64_t *a, svfloat64_t *b, int n);
void svcopy0(svfloat64_t *a, svfloat64_t *b, int n);
void copy(float64_t *a, float64_t *b, int n);
void copy16(float64_t *a, float64_t *b);
#define N 40000
#define NP 40200
#define N8 (N/8)
static double a[NP];
static double b[NP];
void init()
{
int i;
for(i=0;i<NP;i++) a[i]=i;
}
void copy_asm(float64_t *a, float64_t *b, int n)
{
int i,j;
for(i=0;i<n;i+=128){
copy16(a+i, b+i);
}
}
int main(int argc, char *argv[])
{
init();
int nloop = 10000;
int i;
int itest = -1;
if(argc > 1)itest=atoi(argv[1]);
fprintf(stderr, "itest = %d\n", itest);
double* aa;
double* ba;
aa = (double*) (( ((unsigned long long)(a+32))>>8)<<8);
ba = (double*) (( ((unsigned long long)(b+32))>>8)<<8);
fprintf(stderr, "Nloop=%d\n", nloop);
if (itest == 0|| itest <0){
fapp_start("svcopy", 1, 0);
for (i=0;i<nloop;i++){
if (i %1000 == 0) fprintf(stderr, "loop %d\n", i);
svcopy((svfloat64_t*)aa,(svfloat64_t*)ba,N8);
}
fapp_stop("svcopy", 1, 0);
}
if (itest == 1|| itest <0){
fapp_start("copy", 2, 0);
for (i=0;i<nloop;i++){
if (i %1000 == 0) fprintf(stderr, "loop %d\n", i);
copy(aa, ba, N);
}
fapp_stop("copy", 2, 0);
}
if (itest == 4|| itest <0){
fapp_start("copyasm", 5, 0);
for (i=0;i<nloop;i++){
if (i %1000 == 0) fprintf(stderr, "loop %d\n", i);
copy_asm(aa, ba, N);
}
fapp_stop("copyasm", 5, 0);
}
if (itest == 5|| itest <0){
fapp_start("svcopy0", 6, 0);
for (i=0;i<nloop;i++){
if (i %1000 == 0) fprintf(stderr, "loop %d\n", i);
svcopy((svfloat64_t*)aa,(svfloat64_t*)ba,N8);
}
fapp_stop("svcopy0", 6, 0);
}
return 0;
}
2個めのファイル:
// l2testsub.c
#include <stdio.h>
#include <math.h>
#include "fj_tool/fapp.h"
#ifdef __ARM_FEATURE_SVE
#include <arm_sve.h>
#endif /* __ARM_FEATURE_SVE */
void svcopy(float64_t *a, float64_t *b, int n)
{
int i;
svbool_t pg = svptrue_b64();
int vlen= svcntd();
#pragma loop unroll 4
for(i=0;i<n;i+=8){
// fprintf(stderr, "count %d\n", i);
svst1_vnum_f64(pg,( b+i*vlen), 0, svld1_vnum_f64(pg, (a+i*vlen),0));
svst1_vnum_f64(pg,( b+i*vlen), 1, svld1_vnum_f64(pg, (a+i*vlen),1));
svst1_vnum_f64(pg,( b+i*vlen), 2, svld1_vnum_f64(pg, (a+i*vlen),2));
svst1_vnum_f64(pg,( b+i*vlen), 3, svld1_vnum_f64(pg, (a+i*vlen),3));
svst1_vnum_f64(pg,( b+i*vlen), 4, svld1_vnum_f64(pg, (a+i*vlen),4));
svst1_vnum_f64(pg,( b+i*vlen), 5, svld1_vnum_f64(pg, (a+i*vlen),5));
svst1_vnum_f64(pg,( b+i*vlen), 6, svld1_vnum_f64(pg, (a+i*vlen),6));
svst1_vnum_f64(pg,( b+i*vlen), 7, svld1_vnum_f64(pg, (a+i*vlen),7));
}
}
void svcopy0(float64_t *a, float64_t *b, int n)
{
int i;
int vlen= svcntd();
svbool_t pg = svptrue_b64();
#pragma loop noalias
#pragma loop unroll 32
//#pragma loop noprefetch
for(i=0;i<n;i++){
svst1_f64(pg,( b+i*vlen), svld1_f64(pg, (a+i*vlen)));
}
}
void copy(float64_t *a, float64_t *b, int n)
{
int i;
#pragma loop noalias
#pragma loop simd
#pragma loop unroll 32
for(i=0;i<n;i++){
// fprintf(stderr, "count %d\n", i);
b[i] = a[i];
}
}
アセンブラ関数 (copy16.s)
.arch armv8-a+fp16+sve
.file "copy32.c"
.text
.align 2
.p2align 3,,7
.global copy16
.type copy16, %function
copy16:
.LFB0:
.cfi_startproc
ptrue p7.d, ALL
add x0, x0, 512
add x1, x1, 512
ld1d z0.d, p7/z, [x0, -8, mul vl]
ld1d z1.d, p7/z, [x0, -7, mul vl]
ld1d z2.d, p7/z, [x0, -6, mul vl]
ld1d z3.d, p7/z, [x0, -5, mul vl]
ld1d z4.d, p7/z, [x0, -4, mul vl]
ld1d z5.d, p7/z, [x0, -3, mul vl]
ld1d z6.d, p7/z, [x0, -2, mul vl]
ld1d z7.d, p7/z, [x0, -1, mul vl]
ld1d z8.d, p7/z, [x0, 0, mul vl]
ld1d z9.d, p7/z, [x0, 1, mul vl]
ld1d z10.d, p7/z, [x0, 2, mul vl]
ld1d z11.d, p7/z, [x0, 3, mul vl]
ld1d z12.d, p7/z, [x0, 4, mul vl]
ld1d z13.d, p7/z, [x0, 5, mul vl]
ld1d z14.d, p7/z, [x0, 6, mul vl]
ld1d z15.d, p7/z, [x0, 7, mul vl]
st1d z0.d, p7, [x1, -8, mul vl]
st1d z1.d, p7, [x1, -7, mul vl]
st1d z2.d, p7, [x1, -6, mul vl]
st1d z3.d, p7, [x1, -5, mul vl]
st1d z4.d, p7, [x1, -4, mul vl]
st1d z5.d, p7, [x1, -3, mul vl]
st1d z6.d, p7, [x1, -2, mul vl]
st1d z7.d, p7, [x1, -1, mul vl]
st1d z8.d, p7, [x1, 0, mul vl]
st1d z9.d, p7, [x1, 1, mul vl]
st1d z10.d, p7, [x1, 2, mul vl]
st1d z11.d, p7, [x1, 3, mul vl]
st1d z12.d, p7, [x1, 4, mul vl]
st1d z13.d, p7, [x1, 5, mul vl]
st1d z14.d, p7, [x1, 6, mul vl]
st1d z15.d, p7, [x1, 7, mul vl]
ret
.cfi_endproc
.LFE2:
.size copy16, .-copy16
.ident "GCC: (GNU) 8.2.1 20180905 (Red Hat 8.2.1-3)"
.section .note.GNU-stack,"",@progbits
コンパイルオプション等
fcc -Nfjprof -Kfast,ocl,simd=2,optmsg=2,loop_fission,swp_strong \
-O3 -DUNIX -DBSD -DFUJITSU -o l2test l2test.c l2testsub.c copy16.s
サイズ4万の配列を1万回コピーで、A64fx のL2にははいります。念のため
64バイトアライメントをとれるように配列の先頭アドレスをずらしています。
code time time(unaligned)
svcopy0 0.12sec 0.12sec
svcopy 0.12sec -
copy 0.14sec 0.28sec
copyasm16 0.11sec 0.12sec
あと、copyasm16 はアセンブラでコピールーチンをベタ書きしたも
のです。どれも命令数が 1e8 で、 SVE化されていることがわかります。
それを保証するために、copy の場合には色々な pragma つけています。
転送しているデータの量は上り下りともに 3.2GB なので、 0.11sec くらいだ
と 60GB/s の速度です。L2 のバンド幅は 115GB(load) 57GB(store)と資料に
ありますが、これは双方向ではなくて例えば load ばっかりの時に
この速度なので、双方向だと store が 57GB/s の半分、load も同じ、となっ
て、理論限界がでていることがわかります。一方、
イントリンシックで書いたものも L2 リミットの性能はでており、
普通の double で書いたループは2割ほど遅くなるようです。
なお、極めて不思議なのは、 64バイトアライメントをとってない時の動作で、
通常の double で書いたものは遅くなるのですが、 SVEアセンブラベタとか
イントリンシックで書いたものは差がでませんでした。
このような単純なメモリコピーでは、ハードウェアプリフェッチもちゃんと効
いて、高い性能がでることがわかります。