GPGPU、CUDA Fortran、UVA, GPUdirect, peer to peer
マルチGPUを備えたシステム上で、CUDA 4.0 からの機能である Unified Virtual Addressing や GPUdirect v2.0 の機能の使用方法を解説する。今回は、デバイス間の peer-to-peer メモリアクセスを利用する際のプログラムの方法を説明する。
2011年10月6日 Copyright © 株式会社ソフテック 加藤
CUDA 3.2 以前においては、一つのホスト・アプリケーションから複数の GPU (CUDA devices) を使用する場合は、必ず、1スレッド(プロセス)に特定の 1GPU device (以下、「デバイス」と言う。)を割り当てると言う構成でソフトウェアを組む必要がありました。すなわち、一つの「ホスト・スレッド」から「複数」の GPU device(以下、「デバイス」と言う。)を使用することができませんでした。従って、MultiGPU を有する筐体マシンでは、ホスト側において、MPI 等でマルチ・プロセスに分割し、その個々のプロセスに個別のデバイスを割当て、並列 GPU デバイス実行を行うと言う方法がとられてきました。もちろん、こうした構成は現状でも問題はなく、素直なデバイスの使用法と思います。
CUDA 4.0 になると、大きな変化がありました。一番大きな改善点は、ホスト側の一つのスレッド上で、複数のデバイスを切り替えて使用できると言う点です。即ち、複数のデバイスを自由に選択してハンドリングできるため、並列実行プログラミングの自由度が増しました。ホスト側のプログラム・シーケンスの中で、cudaSetDevice() API を使用して特定デバイスを指定すると、これが使用すべき「カレント・デバイス」となります。それ以降のシーケンスでそのデバイスへの命令を発行できます。もし、別のデバイスを使用したいならば、再度 cudaSetDevice() で切り替えると言う方法をとります。こうした方法をとることにより、ホスト側のシングルスレッドのプログラムから、複数のGPUへデータの移動、カーネルプログラムの投入等が自由にできます。これは、マルチ GPU プログラミングが容易になったと言うことに他なりません。
このコラムでは、まず最初に、(1) 一つのホスト側プログラムから2つのGPUデバイス上のデータを ping-pong する CUDA Fortran プログラムを例示します。次に、(2) 片側の GPU 上のカーネル実行において、もう一つのデバイス側のデータを使用する方法を例示します。これらは、CUDA 4.0 (Fermi以上のGPU) 以降でサポートした、Unified Virtual Addressing(UVA) と GPUdirect™ v2.0 peer-to-peer 通信機能を使用することで実現できます。なお、この UVA 関連の機能は、64ビットモードの Linux あるいは、Windows 上で動作する NVIDIA Fermi GPU(compute capability 2.0以上)以降で利用できます。Windows の場合は、さらに TCC ドライバーモードでしか動作しませんので、Tesla GPU 以上のボードの動作だけに制約されます。Linux の場合は、以下のように GeForce 系の GPU でも動作しています。
次回のコラムでは、ホスト側のメモリと Unified Virtual Addressing を行う方法について解説します。
一つのホスト側プログラムから2つのGPUデバイス上のデータを ping-pong する CUDA Fortran プログラムを用いて、UVA 配下でのデバイス間 peer to peer データ移動(GPUdirect v2.0)の方法を説明します。以下に、コメント付きのサンプルプログラムを示します。プログラムの流れは、以下の通りです。
module ping_pong use cudafor ! integer(c_int), parameter :: cudaMemcpyDefault=4 ! Peer-to-Peer Memory Access under Unified virtual addressing ! Ping-Pong Test ! Kato, SofTek contains subroutine pingpong( A, B, C ) implicit none real(4), dimension(:) :: A, B, C real(4), device, allocatable, dimension(:):: Ad0, Bd0 real(4), device, allocatable, dimension(:):: Ad1, Bd1 integer :: N, istat, dev0, dev1, canaccess0_1, canaccess1_0 integer :: ndevices, uva0, uva1 integer :: nreps = 100, i logical :: has_uva type(cudaEvent) :: startEvent, stopEvent real (4) :: time, gbytes ! Properties check type(cudaDeviceProp) :: prop integer :: ilen N = size( A, 1 ) ! アクティブな GPU デバイス数をチェック print *,"\nChecking for multiple GPUs..." istat = cudaGetDeviceCount(ndevices) print '(2x,i2,a)', ndevices, " GPU devices are found." if ( ndevices < 2 ) then print '(2x,a/)', " This system don't have two GPUs and above." stop end if ! 各デバイスのプロパティ(名前)をプリント do i = 0, ndevices - 1 istat = cudaSetDevice(i) istat = cudaGetDeviceProperties(prop,i) ilen = verify(prop%name, ' ', .true.) print '(1x, a, a, i1,a)', prop%name(1:ilen), " (GPU",i, ") is available" end do ! 二つのデバイスに論理デバイス番号を定義 print *, "\n Now, using 2 devices (GPU0 and GPU1) " dev0=0 dev1=1 print *, "\nChecking GPU(s) for support of peer to peer memory access...\n" ! peer-to-peer アクセスが可能なデバイスかをチェック ! Determine if Peer to Peer communication is supported istat = cudaDeviceCanAccessPeer(canaccess0_1,dev0,dev1) ! check dev0 to dev1 istat = cudaDeviceCanAccessPeer(canaccess1_0,dev1,dev0) ! check dev1 to dev0 ! 双方向でpeer-to-peerアクセスが可能であれば、以下実行 if (canaccess0_1 == 1 .and. canaccess1_0 == 1 ) then print '(a, i4/,a,i4/)'," canaccess device 0 to 1 =", canaccess0_1, & " canaccess device 1 to 0 =", canaccess1_0 ! dev0 に対して UVA 機能が使用可能か?尋ねる istat = cudaSetDevice(dev0) istat = cudaGetDeviceProperties(prop,dev0) print '(1x,a,i2)', "device0 supports UVA ? : ",prop%unifiedAddressing uva0 = prop%unifiedAddressing ! dev1 への peer access を enable にする istat = cudaDeviceEnablePeerAccess(dev1,0) ! dev1 に対して UVA 機能が使用可能か?尋ねる istat = cudaSetDevice(dev1) istat = cudaGetDeviceProperties(prop,dev1) print '(1x,a,i2)', "device1 supports UVA ? : ",prop%unifiedAddressing uva1 = prop%unifiedAddressing ! dev0 への peer access を enable にする istat = cudaDeviceEnablePeerAccess(dev0,0) ! 両デバイスともUVA をサポートしているかを確認する ! supprt UVA? has_uva = ( uva0 == 1 .and. uva1 == 1 ) if (has_uva) then print *, "\nBoth GPUs can support UVA, enabling\n" else print *,"\nAt least one of the two GPUs does NOT support UVA\n" stop 999 end if ! current device = dev1、デバイス上に配列割付 ! allocate device 1's arrays allocate( Ad1(N), Bd1(N) ) ! ホスト側 B(1,N) を Bd1(1:N) にコピーする ! copy host data to device 1 (Bd1) Bd1 = B(1:N) !デバイス切替 current device = dev0、デバイス上に配列割付 ! allocate device 0's arrays istat = cudaSetDevice(dev0) allocate( Ad0(N), Bd0(N) ) ! ホスト側 A(1,N) を Ad0(1:N) にコピーする ! copy host data to device 0 (Ad0) Ad0 = A(1:N) ! 時間計測用の event 作成 istat = cudaEventCreate(startEvent) istat = cudaEventCreate(stopEvent) ! timing experiment time = 0.0e0 istat = cudaEventRecord(startEvent, 0) ! ping-pong 往復 50回繰り返し、時間測定 do i = 0, nreps-1 ! ======= ! ping-pong between dev0 and dev1 if (mod(i,2) == 0) then ! copy Ad0 data from device 0 to device 1 i が偶数の場合 istat = cudaMemcpyPeer(Ad1,dev1,Ad0,dev0,N) else ! copy bd1 data from device 1 to device 0 i が奇数の場合 istat = cudaMemcpyPeer(bd0,dev0,bd1,dev1,N) end if ! 以下の API でも通信が可能。PGI 11.8 では cudaMemcpydefault が未サポート ! Direct access to peer memory on "UVA" ! It is necessary that cudaDeviceEnablePeerAccess() is enable. ! istat = cudaMemcpy(Ad1,Ad0,N,cudaMemcpyDeviceToDevice) ! istat = cudaMemcpy(Bd0,Bd1,N,cudaMemcpyDeviceToDevice) ! cannot use 'cudaMemcpydefault' on PGI 11.8. It may not support yet. ! istat = cudaMemcpy(Ad1,Ad0,N,cudaMemcpydefault) ! istat = cudaMemcpy(Bd0,Bd1,N,cudaMemcpydefault) ! ======= end do ! 時間測定終了 istat = cudaEventRecord(stopEvent, 0) istat = cudaThreadSynchronize() istat = cudaEventElapsedTime(time, startEvent, stopEvent) time = time / (nreps*1.0d3) gbytes = N * sizeof(a(1)) / ( 1024.*1024.*1024. ) ! 通信バンド幅性能を印字 print '(a,F7.2,a)', "PingPong performance : ", gbytes*1024. ," Mbytes Xfer Test" print '(1x,a,F6.2,a)', "cudaMemcpyPeer / cudaMemcpy between device 0 and device 1 :", & gbytes/time , " (GB/sec)" ! データ転送の結果を検証、一例で、dev0 にコピーされたBd0(1:N)の値を検証、ホスト側のC配列にコピー ! verify Bd0() as a result : store Bd0 to C array istat = cudaSetDevice(dev0) C(1:N) = Bd0 ! 通信レイテンシーを測定、ここでは 4-byte 送出時のレイテンシー ! Latency (4-byte transfer time) istat = cudaEventCreate(startEvent) istat = cudaEventCreate(stopEvent) ! timing experiment time = 0.0e0 istat = cudaEventRecord(startEvent, 0) ! 4byte dev0 to dev1 通信時間測定 do i = 0, nreps-1 ! copy Ad0 4bytes data from device 0 to device 1 istat = cudaMemcpyPeer(Ad1,dev1,Ad0,dev0,1) end do istat = cudaEventRecord(stopEvent, 0) istat = cudaThreadSynchronize() istat = cudaEventElapsedTime(time, startEvent, stopEvent) time = time / (nreps) / 1.d3 * 1.d6 print '(a,f5.2,a)', " 4-byte Ping Latency (one-way) : ", time ," usec" ! Shutdown devices print *, "\nShutting down...\n" ! 各デバイスのクリーンアップ ! deallocate the arrays on device 1 and cleanup istat = cudaSetDevice(dev1) deallocate( Ad1, Bd1 ) istat = cudaDeviceDisablePeerAccess(dev1) ! deallocate the arrays on device 0 and cleanup istat = cudaSetDevice(dev0) deallocate( Ad0, Bd0 ) istat = cudaDeviceDisablePeerAccess(dev0) istat = cudaEventDestroy(startEvent) istat = cudaEventDestroy(stopEvent) endif !print *, 'error code = ', cudaGetErrorString( cudaGetLastError() ) end subroutine end module program p use ping_pong use cudafor implicit none integer :: nargs, n, i, ierr real, allocatable, dimension(:) :: a, b, c external iargc integer iargc character*10 arg real(8) :: Bytes, Mbytes ! for timing real(8) :: time integer(4) :: hz, clock0, clock1 nargs = iargc() if( nargs == 0 )then print *, 'Instruction : ./a.out xfer-size (Unit: Mbyte)' print *, 'For example, ./a.out 1.0 (this means 1.0MB)' return endif if( nargs >= 1 )then call getarg( 1, arg ) read(arg,'(f)') Mbytes endif if ( Mbytes < 1.0d-8) then print *, "Mbyte-size(argument) should be defined as a floating-point value" stop endif !print *, MBytes Bytes = MBytes * (1024.*1024.) n = Bytes / 4.0 !print *, n , sizeof(a(1)) allocate( a(n), b(n), c(n) ) do i = 1,n a(i) = i b(i) = 2*i c(i) = 0. enddo ! Initialize time function call system_clock(count_rate=hz) ! print *, "system_clock resolution: ", real(1.d0/hz) ! Start a clock call system_clock(count=clock0) call pingpong( a, b, c ) ! ping-pongの実行 call system_clock(count=clock1) ! time= real((clock1-clock0)) / real (hz) ! print *, "Total Time (sec): ",time ! C 配列の内容を検証する ierr = 0 do i = 1,n if( c(i) .ne. b(i) )then ierr = ierr + 1 if( ierr <= 10 )then print 30, i, c(i), b(I) 30 format( 'c(',i,') = ', f12.5, ' should be ', f12.5 ) endif endif enddo if( ierr == 0 )then print *, 'no errors' else print *, ierr, ' errors found' endif end program
現時点での最新バージョン PGI 11.8 を使用してコンパイルを行います。PGI 11.8 では、そのデフォルトが CUDA 3.2 の toolkit を利用しますので、明示的に -Mcuda:4.0 として CUDA 4.0 の toolkit を利用してバイナリを作成する必要があります。以下は、0.1 MB (100KB) 転送を指定したときの実行結果です。実行時の引数に 0.1 を指定します。なお、この引数は、「整数」の指定ではなく、小数点数値で指定して下さい。なお、私の GPU デバイス環境では、通信バンド幅が 4.9GB/sec (仕様上は 6GB/sec?)で飽和するようです。大事な点は、通信レイテンシーが 6 μ秒程度で小さいと言うことが挙げられます。ここで示した性能は、UVA下の GPUdirect v2.0 の機能を使用したもので、PCIバスに繋がるホスト側インタフェースを経由しない device to device の性能です。
[kato@photon29]$ pgf90 -Mcuda:4.0 pingpong.cuf -V PGF90/x86-64 Linux 11.8-0 Copyright 1989-2000, The Portland Group, Inc. All Rights Reserved. Copyright 2000-2011, STMicroelectronics, Inc. All Rights Reserved. [kato@photon29]$ ./a.out 0.1 Checking for multiple GPUs... 2 GPU devices are found. GeForce GTX 580 (GPU0) is available GeForce GTX 480 (GPU1) is available Now, using 2 devices (GPU0 and GPU1) Checking GPU(s) for support of peer to peer memory access... "1" means "Yes", "0" means No" canaccess device 0 to 1 = 1 canaccess device 1 to 0 = 1 device0 supports UVA ? : 1 device1 supports UVA ? : 1 Both GPUs can support UVA, enabling PingPong performance : 0.10 Mbytes Xfer Test cudaMemcpyPeer / cudaMemcpy between device 0 and device 1 : 3.15 (GB/sec) 4-byte Ping Latency (one-way) : 5.78 usec Shutting down... no errors
それでは、ホスト側インタフェースを staging として経由すると peer-to-peer 性能はどのようになるでしょうか?上記のプログラムをちょっと変更すると、その性能を測定できます。以下のように、cudaDeviceEnablePeerAccess()をコメント化して、PeerAccessを disable にすることです。この状態で、cudaMemcpyPeer() APIを使用しても、device to device コピーは正常に動作しますが、その性能は劣化します。小さいデータ転送時のバンド幅が劣化し、さらに、レイテンシーも 20 μ秒弱に増加しています。一般に、バンド幅より、レイテンシーの方がアプリケーション性能に寄与しますので、CUDA 4.0 の inter-Device のインタフェースは、デバイス間の低レイテンシーの通信機能を提供するものとなります。
上記のソースの中の以下の関数をコメント化する。 ! istat = cudaDeviceEnablePeerAccess(dev1,0) ! istat = cudaDeviceEnablePeerAccess(dev0,0)
[kato@photon29]$ ./a.out 0.1 (snip) PingPong performance : 0.10 Mbytes Xfer Test cudaMemcpyPeer / cudaMemcpy between device 0 and device 1 : 1.93 (GB/sec) 4-byte Ping Latency (one-way) : 19.88 usec Shutting down...
さて、peer-to-peer メモリコピーを行う API は他にもありますが、これらは内部的には全て cudaMemcpyPeer() 関数に置換され使用されます。例えば、cudaMemcpy() の kind = cudaMemcpyDeviceToDevice と指定した場合、CUDA 3.2 以前は、1 デバイス内のメモリコピー機能でしたが、もし、cudaDeviceEnablePeerAccess() を enable にしておけば、デバイスが異なっていても、コピーできます。また、UVA 環境下では、cudaMemcpy() の kind 引数に、cudaMemcpydefault が使用できるようになりましたが、CUDA Fortran 11.8/11.9 リリースでは、まだ、サポートしておりません。cudaMemcpydefault は、runtime 時にドライバーが src, dst 等のデバイス(ポインタ)等を判断するように指示するための引数なので、あえて使用しなくても良いと思います。プログラム・コーディング上は明示的な Memcpy 等の API を使用した方が、間違いがなく、後々分かり易い記述となります。
! Direct access to peer memory on "UVA" ! It is necessary that cudaDeviceEnablePeerAccess() is enable. istat = cudaMemcpy(Ad1,Ad0,N,cudaMemcpyDeviceToDevice) istat = cudaMemcpy(Bd0,Bd1,N,cudaMemcpyDeviceToDevice) ! cannot use 'cudaMemcpydefault' on PGI 11.8. It may not support yet. istat = cudaMemcpy(Ad1,Ad0,N,cudaMemcpydefault) istat = cudaMemcpy(Bd0,Bd1,N,cudaMemcpydefault)
次に、実際の kernel プログラムの中で、異なるデバイス上のデータを使用する方法の一例を以下に記します。プログラムの前半部は、上述(1) のプログラムと同じです。GPUデバイスのチェックを行って、cudaDeviceEnablePeerAccess() を enable にしています。これを行うことが、GPUdirect 機能を使うために必須条件となります。以下のプログラム例では、vadd_kernel と言うカーネルプログラムの引数に、使用する配列(アドレス)を渡します。この引数に、異なるデバイスの配列名を記すことで、異なるデバイス・メモリ上のデータをアクセスして演算を進めることになります。もちろん、こうした構成の場合、その演算性能は、デバイス上の自身のメモリデータを使うより、性能が大きく劣化します。これは、演算中、必要なデータを peer to peer でコピーするオーバーヘッドが加算される訳ですから、当然のことです。皆さん、誤解してはならないことは、UVA や peer to peer アクセスが可能なプログラム環境とは、簡単に言えばプログラムの書き易さを提供するものであり、一方、その性能は必ず劣化します。どう言った所に UVA 構成を利用するのか、予め性能設計が必要であり、適材適所で使い分ける必要があります。性能を考えた場合、まあ、MPIコーディングと同じ考えで、並列分割時の袖データの交換にのみ使うような構成で使用すると言うのが、一つの方法なのでしょう。
! using UVA
! launch the kernel on device 1 using Ad0/Bd0 on device0 memory
! device 1 に投入する kernel 引数に device 0 上のデータ Ad0, Bd0 のアドレスを渡す
istat = cudaSetDevice(dev1)
call vadd_kernel <<< (N+63)/64, 64 >>> ( Ad0, Bd0, Cd1, N )
module vector_add use cudafor ! Peer-to-Peer Memory Access under Unified virtual addressing ! Vector add kernel using another device memory data. ! Kato, SofTek contains attributes(global) subroutine vadd_kernel(A,B,C,N) implicit none real(4), device :: A(N), B(N), C(N) integer, value :: N integer :: i i = (blockidx%x-1)*blockdim%x + threadidx%x if( i <= N ) C(i) = A(i) + B(I) ! if( i <= N ) print *, c(i) end subroutine subroutine vadd( A, B, C ) implicit none real(4), dimension(:) :: A, B, C real(4), device, allocatable, dimension(:):: Ad0, Bd0 real(4), device, allocatable, dimension(:):: Ad1, Bd1, Cd1 integer :: i, N, istat, dev0, dev1, canaccess0_1, canaccess1_0 integer ndevices, uva0, uva1 integer :: nreps = 1000 logical has_uva type(cudaEvent) :: startEvent, stopEvent real (4) :: time ! Properties check type(cudaDeviceProp) :: prop integer :: ilen N = size( A, 1 ) print *,"\nChecking for multiple GPUs..." istat = cudaGetDeviceCount(ndevices) print '(2x,i2,a)', ndevices, " GPU devices are found." if ( ndevices < 2 ) then print '(2x,a/)', " This system don't have two GPUs and above." stop end if do i = 0, ndevices - 1 istat = cudaSetDevice(i) istat = cudaGetDeviceProperties(prop,i) ilen = verify(prop%name, ' ', .true.) print '(1x, a, i1, 5x, a, a, i1,a)',"device", i, prop%name(1:ilen), " (GPU",i, ") is available" end do print *, "\n Now, using 2 devices (GPU0 and GPU1) " dev0 = 0 dev1 = 1 print *, "\nChecking GPU(s) for support of peer to peer memory access...\n" ! Determine if Peer to Peer communication is supported istat = cudaDeviceCanAccessPeer(canaccess0_1,dev0,dev1) istat = cudaDeviceCanAccessPeer(canaccess1_0,dev1,dev0) if (canaccess0_1 == 1 .and. canaccess1_0 == 1 ) then print '(a, i4/,a,i4/)'," canaccess device 0 to 1 =", canaccess0_1, & " canaccess device 1 to 0 =", canaccess1_0 istat = cudaSetDevice(dev0) istat = cudaGetDeviceProperties(prop,dev0) print '(1x,a,i2)', "device0 supports UVA ? : ",prop%unifiedAddressing uva0 = prop%unifiedAddressing istat = cudaDeviceEnablePeerAccess(dev1,0) istat = cudaSetDevice(dev1) istat = cudaGetDeviceProperties(prop,dev1) print '(1x,a,i2)', "device1 supports UVA ? : ",prop%unifiedAddressing uva1 = prop%unifiedAddressing istat = cudaDeviceEnablePeerAccess(dev0,0) ! supprt UVA? has_uva = ( uva0 == 1 .and. uva1 == 1 ) if (has_uva) then print *, "\nBoth GPUs can support UVA, enabling\n" else print *,"\nAt least one of the two GPUs does NOT support UVA\n" stop 999 end if ! allocate device 1's arrays allocate( Ad1(N), Bd1(N), Cd1(N) ) ! allocate device 0's arrays istat = cudaSetDevice(dev0) allocate( Ad0(N), Bd0(N) ) ! copy host data to device 0 Ad0 = A(1:N) Bd0 = B(1:N) ! using UVA ! launch the kernel on device 1 using Ad0/Bd0 on device0 memory ! device 1 に投入する kernel 引数に device 0 上のデータ Ad0, Bd0 のポインタを渡す print *, "Run kernel on GPU 1, taking GPU 0 data(Ad0,Bd0), the result is on GPU1." istat = cudaSetDevice(dev1) istat = cudaEventCreate(startEvent) istat = cudaEventCreate(stopEvent) ! timing experiment time = 0.0e0 istat = cudaEventRecord(startEvent, 0) do i = 1, nreps call vadd_kernel <<< (N+63)/64, 64 >>> ( Ad0, Bd0, Cd1, N ) enddo istat = cudaEventRecord(stopEvent, 0) istat = cudaThreadSynchronize() istat = cudaEventElapsedTime(time, startEvent, stopEvent) time = time / (nreps) * 1.e3 !(usec) print '(1x,a,F8.2)', "Total Time (usec): ",time ! get the results from device 1 for verification C(1:N) = Cd1 ! launch the kernel on device 1 using Ad1/Bd1 on device1 memory ! device 1 に投入する kernel 引数に device 1 上のデータ Ad1, Bd1 のポインタを渡す print *, "\n Run kernel on GPU 1, using GPU 1 data(Ad1,Bd1), the result is on GPU1." ! timing experiment time = 0.0e0 istat = cudaEventRecord(startEvent, 0) do i = 1, nreps call vadd_kernel <<< (N+63)/64, 64 >>> ( Ad1, Bd1, Cd1, N ) enddo istat = cudaEventRecord(stopEvent, 0) istat = cudaThreadSynchronize() istat = cudaEventElapsedTime(time, startEvent, stopEvent) time = time / (nreps) * 1.e3 !(usec) print '(1x,a,F8.2)', "Total Time (usec): ",time ! deallocate the arrays on device 0 and cleanup deallocate( Ad1, Bd1, Cd1 ) istat = cudaDeviceDisablePeerAccess(dev1) ! deallocate the arrays on device 1 and cleanup istat = cudaSetDevice(dev0) deallocate( Ad0, Bd0 ) istat = cudaDeviceDisablePeerAccess(dev0) endif end subroutine end module program p use vector_add use cudafor implicit none integer :: nargs, n, i, ierr real, allocatable, dimension(:) :: a, b, c external iargc integer iargc character*10 arg ! for timing ! real(8) :: time integer(4) :: hz, clock0, clock1 nargs = iargc() if( nargs == 0 )then print *, 'Instruction : ./a.out N-size' return endif if( nargs >= 1 )then call getarg( 1, arg ) read(arg,'(i)') n endif allocate( a(n), b(n), c(n) ) do i = 1,n a(i) = i b(i) = 2*i c(i) = 0. enddo ! Initialize time function ! call system_clock(count_rate=hz) ! print *, "system_clock resolution: ", real(1.d0/hz) ! Start a clock ! call system_clock(count=clock0) call vadd( a, b, c ) ! call system_clock(count=clock1) ! time= real((clock1-clock0)) / real (hz) ! print *, "Total Time (sec): ",time ierr = 0 do i = 1,n if( c(i) .ne. a(i) + b(i) )then ierr = ierr + 1 if( ierr <= 10 )then print 30, i, c(i), a(i)+b(I) 30 format( 'c(',i,') = ', f12.5, ' should be ', f12.5 ) endif endif enddo if( ierr == 0 )then print *, '\n no errors' else print *, ierr, '\n errors found' endif end program
このプログラムも、現時点での最新バージョン PGI 11.8 を使用してコンパイルを行います。PGI 11.8 では、デフォルトが CUDA 3.2 の toolkit を利用しますので、明示的に -Mcuda:4.0 として CUDA 4.0 の toolkit を利用してバイナリを作成してください。実行時の引数には、何個の vector add 計算を行うかと言う個数を指定します。以下の例では 100000個の加算演算を行うことになります。さて、このプログラムの中には、同じデバイス上のデータを使用して演算した時間計測も行っております。これが、いわゆる GPU の primitive な性能ということになりますが、どの程度の性能差が現れるのかがよく分かります。以下の例では、性能で10倍以上の開きがあることが分かります。
[kato@photon29 2585-cuda40feature]$ pgf90 -Mcuda:4.0 -fast p2p.cuf [kato@photon29 2585-cuda40feature]$ a.out 100000 Checking for multiple GPUs... 2 GPU devices are found. device0 GeForce GTX 580 (GPU0) is available device1 GeForce GTX 480 (GPU1) is available Now, using 2 devices (GPU0 and GPU1) Checking GPU(s) for support of peer to peer memory access... canaccess device 0 to 1 = 1 canaccess device 1 to 0 = 1 device0 supports UVA ? : 1 device1 supports UVA ? : 1 Both GPUs can support UVA, enabling Run kernel on GPU 1, taking GPU 0 data(Ad0,Bd0), the result is on GPU1. Total Time (usec): 162.24 Run kernel on GPU 1, using GPU 1 data(Ad1,Bd1), the result is on GPU1. Total Time (usec): 13.95 no errors