Previous ToC Next

149. A64fx におけるイントリンシック

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アセンブラベタとか イントリンシックで書いたものは差がでませんでした。

このような単純なメモリコピーでは、ハードウェアプリフェッチもちゃんと効 いて、高い性能がでることがわかります。
Previous ToC Next