インテルXeon Phiに精通し続けています:「ネイティブ」コード

前回の記事で、オフロードを使用したIntel Xeon Phiコプロセッサーに関する知識について説明しました。メインコードはホストで実行され、個々のブロックはコプロセッサーにアップロードされます。 この記事では、「ネイティブ」コードをコンパイルして使用することを検討し、それが与えるものとそれを脅かすものを見つけます。 投稿の最後に、Fortranとサンプルプログラムの使用に関する4つの提案があります。



この記事は、ソフトウェアまたはハードウェア製品の宣伝または反宣伝ではなく、著者の個人的な経験のみを説明しています。

前回と同様に、身体の相互作用の問題(n体問題)を検討します。 前の記事からCPUの問題の解決策を講じ、必要に応じて、MIC(以降Intel Xeon Phiと呼ぶ)で実行するようにコードを変更します。

OpenMPを使用した並列コード
/*---------------------------------------------------------*/ /* N-Body simulation benchmark */ /* written by MSOzhgibesov */ /* 04 July 2015 */ /*---------------------------------------------------------*/ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #include <time.h> #include <omp.h> #define HOSTLEN 50 int numProc; // Initial conditions void initCoord(float *rA, float *vA, float *fA, \ float initDist, int nBod, int nI); // Forces acting on each body void forces(float *rA, float *fA, int nBod); // Calculate velocities and update coordinates void integration(float *rA, float *vA, float *fA, int nBod); int main(int argc, const char * argv[]) { int const nI = 32; // Number of bodies in X, Y and Z directions int const nBod = nI*nI*nI; // Total Number of bodies int const maxIter = 20; // Total number of iterations (time steps) float const initDist = 1.0; // Initial distance between the bodies float *rA; // Coordinates float *vA; // Velocities float *fA; // Forces int iter; double startTime0, endTime0; char host[HOSTLEN]; rA = (float*)malloc(3*nBod*sizeof(float)); fA = (float*)malloc(3*nBod*sizeof(float)); vA = (float*)malloc(3*nBod*sizeof(float)); gethostname(host, HOSTLEN); printf("Host name: %s\n", host); numProc = omp_get_num_procs(); printf("Available number of processors: %d\n", numProc); // Setup initial conditions initCoord(rA, vA, fA, initDist, nBod, nI); startTime0 = omp_get_wtime(); // Main loop for ( iter = 0; iter < maxIter; iter++ ) { forces(rA, fA, nBod); integration(rA, vA, fA, nBod); } endTime0 = omp_get_wtime(); printf("\nTotal time = %10.4f [sec]\n", endTime0 - startTime0); free(rA); free(vA); free(fA); return 0; } // Initial conditions void initCoord(float *rA, float *vA, float *fA, \ float initDist, int nBod, int nI) { int i, j, k; float Xi, Yi, Zi; float *rAx = &rA[ 0]; //---- float *rAy = &rA[ nBod]; // Pointers on X, Y, Z components of coordinates float *rAz = &rA[2*nBod]; //---- int ii = 0; memset(fA, 0.0, 3*nBod*sizeof(float)); memset(vA, 0.0, 3*nBod*sizeof(float)); for (i = 0; i < nI; i++) { Xi = i*initDist; for (j = 0; j < nI; j++) { Yi = j*initDist; for (k = 0; k < nI; k++) { Zi = k*initDist; rAx[ii] = Xi; rAy[ii] = Yi; rAz[ii] = Zi; ii++; } } } } // Forces acting on each body void forces(float *rA, float *fA, int nBod) { int i, j; float Xi, Yi, Zi; float Xij, Yij, Zij; // X[j] - X[i] and so on float Rij2; // Xij^2+Yij^2+Zij^2 float invRij2, invRij6; // 1/rij^2; 1/rij^6 float *rAx = &rA[ 0]; //---- float *rAy = &rA[ nBod]; // Pointers on X, Y, Z components of coordinates float *rAz = &rA[2*nBod]; //---- float *fAx = &fA[ 0]; //---- float *fAy = &fA[ nBod]; // Pointers on X, Y, Z components of forces float *fAz = &fA[2*nBod]; //---- float magForce; // Force magnitude float const EPS = 1.E-10; // Small value to prevent 0/0 if i==j #pragma omp parallel for num_threads(numProc) private(Xi, Yi, Zi, \ Xij, Yij, Zij, magForce, invRij2, invRij6, j, i) for (i = 0; i < nBod; i++) { Xi = rAx[i]; Yi = rAy[i]; Zi = rAz[i]; fAx[i] = 0.0; fAy[i] = 0.0; fAz[i] = 0.0; for (j = 0; j < nBod; j++) { Xij = rAx[j] - Xi; Yij = rAy[j] - Yi; Zij = rAz[j] - Zi; Rij2 = Xij*Xij + Yij*Yij + Zij*Zij; invRij2 = Rij2/((Rij2 + EPS)*(Rij2 + EPS)); invRij6 = invRij2*invRij2*invRij2; magForce = 6.f*invRij2*(2.f*invRij6 - 1.f)*invRij6; fAx[i]+= Xij*magForce; fAy[i]+= Yij*magForce; fAz[i]+= Zij*magForce; } } } // Integration of coordinates an velocities void integration(float *rA, float *vA, float *fA, int nBod) { int i; float const dt = 0.01; // Time step float const mass = 1.0; // mass of a body float const mdthalf = dt*0.5/mass; float *rAx = &rA[ 0]; float *rAy = &rA[ nBod]; float *rAz = &rA[2*nBod]; float *vAx = &vA[ 0]; float *vAy = &vA[ nBod]; float *vAz = &vA[2*nBod]; float *fAx = &fA[ 0]; float *fAy = &fA[ nBod]; float *fAz = &fA[2*nBod]; #pragma omp parallel for num_threads(numProc) private(i) for (i = 0; i < nBod; i++) { rAx[i]+= (vAx[i] + fAx[i]*mdthalf)*dt; rAy[i]+= (vAy[i] + fAy[i]*mdthalf)*dt; rAz[i]+= (vAz[i] + fAz[i]*mdthalf)*dt; vAx[i]+= fAx[i]*dt; vAy[i]+= fAy[i]*dt; vAz[i]+= fAz[i]*dt; } }
      
      







コプロセッサー上のコードは、次の2つの方法で起動できます。



最後にアンロードの作業を検討したとき 、今回はMICの「ネイティブ」コードを収集して実行しようとします。

この方法を使用すると、最小限の変更で既存のプログラムをコプロセッサーで実行できます。 ただし、次の点を考慮する必要があります。



MIC用に作成された実行可能ファイルは、scp(Intel Xeon Phiには独自のLinux派生マイクロOSがあります)を使用してコプロセッサーにコピーされ、起動します。



MICへのユーザーの作成/追加



  1. 追加するユーザー(micuser)の下で、sshキーを作成します。

     $ ssh-keygen
          
          





    保存されたパスを覚えておいてください:/home/micuser/.ssh/

  2. ルートの下で、MICの新しいユーザーを作成します。

     $ micctrl –-useradd=micuser –-uid=500 –-gid=500 –-sshkeys=/home/micuser/.ssh/
          
          





    ここで、uidとgidはユーザーIDとグループIDです。



sshキーを使用してディレクトリを指定しない場合、ユーザーとしてログインすることはできません-知らないパスワードを要求します。 Xeon Phi管理プロセスの詳細な説明。 MICでユーザーを作成する別の方法 :コプロセッサーにrootとしてログインし(デフォルトでは、rootのみがssh経由でMICにアクセスできます)、useraddを使用してユーザーを作成します。 私は2番目の方法をチェックしませんでした-公式ガイドに従い、起こりうる不具合には対処したくない



マイクに行く



CPUのプログラムを最小限の変更でMICで使用できるというステートメントを検証するには、最初にCPUバージョンのプログラムを使用します。 MIC用にコンパイルし、コピーして実行します。

 $ icc nbody_CPU.c -mmic -openmp -O3 -o nbdMIC.run $ scp nbdMIC.run mic0: $ ssh mic0 $ ./nbdMIC.run ./nbdMIC.run: error while loading shared libraries: libiomp5.so: cannot open shared object file: No such file or directory
      
      





それはまったく面白くない-どこかで台無しにした! 実際、ほとんどどこにもありません-結論としては、Xeon Phiは独自のファイルシステムを備えた個別のデバイスであり、デフォルトではあまり知らないということです! 解決策は簡単です。実行可能プログラムだけでなくMICにもコピーする必要があります。 ホストに移動してコピーします(ライブラリだけでなく、MIC用にコンパイルされたライブラリもコピーしていることに注意してください)。

 $ scp /opt/intel/composer_xe_2013_sp1.2.144/compiler/lib/mic/libiomp5.so mic0:/tmp/ $ ssh mic0 $ echo $LD_LIBRARY_PATH $ export LD_LIBRARY_PATH=/tmp $ ./nbdMIC.run Host name: mic0.local Available number of processors: 240 Total time = 1.0823 [sec]
      
      





ここで、2つの興味深いことがわかります。

  1. 使用可能なストリームの数は240( Intel Xeon 5110Pには60の物理コアがあります )であり、アンロードを使用する場合の236ではありません。
  2. 「ネイティブ」コードの実行速度は、ダウンロード可能なコードの約1.3倍(1.08秒対1.44秒)です。


アンロードを伴うプログラムの結果




アンロードの場合、ホストとの対話を確実にするために1つのコアがオフロードデーモン与えられ 、「ネイティブ」コードは利用可能なすべてのリソースによって実行されます。

速度の向上は、ホストとMIC間のデータ交換がほぼ完全にないこと(印刷するものを除く)、および追加のコンピューティングコア(それほどではありませんが)によるものです。

上記のように、コプロセッサーは別個のデバイスであるため、コピーされたライブラリー、実行可能ファイル自体はどこかに保存する必要がありますが、コプロセッサーには独自のSSD / HDD(少なくとも5110P)はありません。 その後、すべてがコピーされますか? 答えは簡単です。RAMにコピーされます。 したがって、コピーされたファイルごとに、プログラムの実行に使用できるRAMのサイズが小さくなります。 そして、プログラムの出力で数ギガバイトのファイルを取得する場合はどうなりますか? このような目的のために、ホストからMICにフォルダーをマウントできます。

退屈なタスクは、必要なすべてのライブラリを「抽出」してコピーすることでもあります。幸い、コンパイルされたプログラムのすべての依存関係を判別できるmicnativeloadexユーティリティがあります。 このユーティリティのアプリケーションの説明、およびディレクトリのマウント方法については、 こちらをご覧ください



Fortranに関する4つのオファー



最後の記事では、Cのみで行われたIntel Xeon Phiコプロセッサーの最初の知人について説明しました。同時に、Fortran言語を使用する可能性についても言及しましたが、状況を修正する要求を受け取った結果、これを正確に行う方法についての説明はありませんでした。 主なアイデアは、Fortranを使用する場合、C言語は変更せず、ディレクティブの構文のみが変更されるということです。 したがって、以下はFortranプログラムのソースコードのみです。

CPU用のFortranプログラム
 !---------------------------------------------------------! ! N-Body simulation benchmark ! ! written by MSOzhgibesov ! ! 14 July 2015 ! !---------------------------------------------------------! program nbody_CPU use omp_lib implicit none integer, parameter:: nI = 32 ! Number of bodies in X, Y and Z directions integer, parameter:: nBod = nI**3 ! Total Number of bodies integer, parameter:: maxIter = 20 ! Total number of iterations (time steps) integer:: numProc ! Number of available processors integer:: iter character(len=50):: host real(4), parameter:: initDist = 1.0 ! Initial distance between the bodies real(4), allocatable:: rA(:) ! Coordinates real(4), allocatable:: vA(:) ! Velocities real(4), allocatable:: fA(:) ! Forces real(8):: startTime0, endTime0 common/ourCommonData/numProc allocate(rA(3*nBod), vA(3*nBod), fA(3*nBod)) call hostnm(host) write(*,'(A11,A50)')"Host name: ", host numProc = omp_get_num_procs() write(*,'(A32,I4)')"Available number of processors: ",numProc ! Setup initial conditions call initCoord(rA, vA, fA, initDist, nBod, nI) ! Main loop startTime0 = omp_get_wtime() do iter = 1, maxIter call forces(rA, vA, nBod) call integration(rA, vA, fA, nBod) enddo endTime0 = omp_get_wtime() write(*,'(A13,F10.4,A6)'), "Total time = ", endTime0 - startTime0," [sec]" deallocate(rA, vA, fA) end program ! Initial conditions subroutine initCoord(rA, vA, fA, initDist, nBod, nI) implicit none integer:: i, j, k, ii integer:: nI, nBod integer:: initDist integer:: numProc real(4):: Xi, Yi,Zi real(4):: rA(*), fA(*), vA(*) fA(1:3*nBod) = 0.E0 vA(1:3*nBod) = 0.E0 ii = 1 do i = 1, nI Xi = i*(initDist - 1) do j = 1, nI Yi = j*(initDist - 1) do k = 1, nI Zi = k*(initDist - 1) rA(ii ) = Xi rA(ii+ nBod) = Yi rA(ii+2*nBod) = Zi ii = ii + 1 enddo enddo enddo end subroutine initCoord ! Forces acting on each body subroutine forces(rA, fA, nBod) use omp_lib implicit none integer:: i, j integer:: nI, nBod integer:: numProc real(4):: Xi, Yi, Zi real(4):: Xij, Yij, Zij ! X[j] - X[i] and so on real(4):: Rij2 ! Xij^2+Yij^2+Zij^2 real(4):: invRij2, invRij6 ! 1/rij^2; 1/rij^6 real(4):: rA(*), fA(*) real(4):: magForce ! Force magnitude real(4):: fAix, fAiy, fAiz real(4), parameter:: EPS = 1.E-10 ! Small value to prevent 0/0 if i==j common/ourCommonData/numProc !$OMP PARALLEL NUM_THREADS(numProc) & !$OMP PRIVATE(Xi, Yi, Zi, Xij, Yij, Zij, magForce, invRij2, invRij6, i, j)& !$OMP PRIVATE(fAix, fAiy, fAiz) !$OMP DO do i = 1, nBod Xi = rA(i ) Yi = rA(i+ nBod) Zi = rA(i+2*nBod) fAix = 0.E0 fAiy = 0.E0 fAiz = 0.E0 do j = 1, nBod Xij = rA(j ) - Xi Yij = rA(j+ nBod) - Yi Zij = rA(j+2*nBod) - Zi Rij2 = Xij*Xij + Yij*Yij + Zij*Zij invRij2 = Rij2/((Rij2 + EPS)**2) invRij6 = invRij2*invRij2*invRij2 magForce = 6.0*invRij2*(2.0*invRij6 - 1.0)*invRij6 fAix = fAix + Xij*magForce fAiy = fAiy + Yij*magForce fAiz = fAiz + Zij*magForce enddo fA(i ) = fAix fA(i+ nBod) = fAiy fA(i+2*nBod) = fAiz enddo !$OMP END PARALLEL end subroutine forces subroutine integration(rA, vA, fA, nBod) use omp_lib implicit none integer:: i integer:: nI, nBod integer:: numProc real(4), parameter:: dt = 0.01 ! Time step real(4), parameter:: mass = 1.0 ! mass of a body real(4), parameter:: mdthalf = dt*0.5/mass real(4):: rA(*), vA(*), fA(*) common/ourCommonData/numProc !$OMP PARALLEL NUM_THREADS(numProc) PRIVATE(i) !$OMP DO do i = 1, 3*nBod rA(i) = (rA(i) + fA(i)*mdthalf)*dt vA(i) = fA(i)*dt enddo !$OMP END PARALLEL end subroutine integration
      
      







Fortran Xeon Phiアップロードプログラム
 !---------------------------------------------------------! ! N-Body simulation benchmark ! ! written by MSOzhgibesov ! ! 14 July 2015 ! !---------------------------------------------------------! program nbody_XeonPhi use omp_lib implicit none integer, parameter:: nI = 32 ! Number of bodies in X, Y and Z directions integer, parameter:: nBod = nI**3 ! Total Number of bodies integer, parameter:: maxIter = 20 ! Total number of iterations (time steps) integer:: numProc integer:: iter character(len=50):: host real(4), parameter:: initDist = 1.0 ! Initial distance between the bodies real(4), allocatable:: rA(:) ! Coordinates real(4), allocatable:: vA(:) ! Velocities real(4), allocatable:: fA(:) ! Forces real(8):: startTime0, endTime0 common/ourCommonData/numProc allocate(rA(3*nBod), vA(3*nBod), fA(3*nBod)) ! Mark variable numProc as needing to be allocated ! on both the host and device !DIR$ ATTRIBUTES OFFLOAD:mic::numProc, hostnm !DIR$ OFFLOAD BEGIN TARGET(mic) OUT(host, numProc) call hostnm(host) numProc = omp_get_num_procs() !DIR$ END OFFLOAD write(*,'(A11,A50)')"Host name: ", host write(*,'(A32,I4)')"Available number of processors: ",numProc ! Setup initial conditions call initCoord(rA, vA, fA, initDist, nBod, nI) ! Mark routines integration and forces as needing both ! host and coprocessor version !DIR$ ATTRIBUTES OFFLOAD:mic::integration, forces ! Main loop startTime0 = omp_get_wtime() !DIR$ OFFLOAD BEGIN TARGET(mic) INOUT(rA,fA,vA:length(3*nBod)) do iter = 1, maxIter call forces(rA, vA, nBod) call integration(rA, vA, fA, nBod) enddo !DIR$ END OFFLOAD endTime0 = omp_get_wtime() write(*,'(A13,F10.4,A6)'), "Total time = ", endTime0 - startTime0," [sec]" deallocate(rA, vA, fA) end program nbody_XeonPhi ! Initial conditions subroutine initCoord(rA, vA, fA, initDist, nBod, nI) implicit none integer:: i, j, k, ii integer:: nI, nBod integer:: initDist integer:: numProc real(4):: Xi, Yi,Zi real(4):: rA(*), fA(*), vA(*) fA(1:3*nBod) = 0.D0 vA(1:3*nBod) = 0.D0 ii = 1 do i = 1, nI Xi = i*(initDist - 1) do j = 1, nI Yi = j*(initDist - 1) do k = 1, nI Zi = k*(initDist - 1) rA(ii ) = Xi rA(ii+ nBod) = Yi rA(ii+2*nBod) = Zi ii = ii + 1 enddo enddo enddo end subroutine initCoord ! Forces acting on each body !DIR$ ATTRIBUTES OFFLOAD:mic:: forces subroutine forces(rA, fA, nBod) implicit none integer:: i, j integer:: nI, nBod integer:: numProc real(4):: Xi, Yi, Zi real(4):: Xij, Yij, Zij ! X[j] - X[i] and so on real(4):: Rij2 ! Xij^2+Yij^2+Zij^2 real(4):: invRij2, invRij6 ! 1/rij^2; 1/rij^6 real(4):: rA(*), fA(*) real(4):: magForce ! Force magnitude real(4):: fAix, fAiy, fAiz real(4), parameter:: EPS = 1.E-10 ! Small value to prevent 0/0 if i==j common/ourCommonData/numProc !$OMP PARALLEL NUM_THREADS(numProc) & !$OMP PRIVATE(Xi, Yi, Zi, Xij, Yij, Zij, magForce, invRij2, invRij6, i, j)& !$OMP PRIVATE(fAix, fAiy, fAiz) !$OMP DO do i = 1, nBod Xi = rA(i ) Yi = rA(i+ nBod) Zi = rA(i+2*nBod) fAix = 0.E0 fAiy = 0.E0 fAiz = 0.E0 do j = 1, nBod Xij = rA(j ) - Xi Yij = rA(j+ nBod) - Yi Zij = rA(j+2*nBod) - Zi Rij2 = Xij*Xij + Yij*Yij + Zij*Zij invRij2 = Rij2/((Rij2 + EPS)**2) invRij6 = invRij2*invRij2*invRij2 magForce = 6.0*invRij2*(2.0*invRij6 - 1.0)*invRij6 fAix = fAix + Xij*magForce fAiy = fAiy + Yij*magForce fAiz = fAiz + Zij*magForce enddo fA(i ) = fAix fA(i+ nBod) = fAiy fA(i+2*nBod) = fAiz enddo !$OMP END PARALLEL end subroutine forces !DIR$ ATTRIBUTES OFFLOAD:mic::integration subroutine integration(rA, vA, fA, nBod) implicit none integer:: i integer:: nI, nBod integer:: numProc real(4), parameter:: dt = 0.01 ! Time step real(4), parameter:: mass = 1.0 ! mass of a body real(4), parameter:: mdthalf = dt*0.5/mass real(4):: rA(*), vA(*), fA(*) common/ourCommonData/numProc !$OMP PARALLEL NUM_THREADS(numProc) PRIVATE(i) !$OMP DO do i = 1, 3*nBod rA(i) = (rA(i) + fA(i)*mdthalf)*dt vA(i) = fA(i)*dt enddo !$OMP END PARALLEL end subroutine integration
      
      









結論の代わりに



「ネイティブ」コードでの作業は、いくつかの点で、ダウンロードするよりも簡単です。CPUで利用可能なプログラムを使用できます。さらに、「ネイティブ」プログラムはオフロードよりも高速に機能します。 同時に、プログラムがサードパーティのライブラリに依存している場合は、MIC用に再コンパイルするか、代替を探す必要があることに留意してください。 また、コプロセッサーにコピーされたファイルはRAMに保存されますが、RAMはそれほど多くないことに注意してください。

最後の記事へのコメントの1つで、Xeon PhiとCUDA GPUのパフォーマンスを比較する問題が提起されました。一方ではすべてタスクに依存し、他方では比較するのが興味深いです。 次の記事では、誰がより高速であるかを確認し、デバイスの努力を組み合わせようとします。



All Articles