Building Mini-Supercomputer with NanoPi-NEO4 (3nodes/18cores)

# Abstract

This article explains how to build a Mini-Supercomputer with three NanoPi-NEO4's.
Basically, Procedure is almost same as SUGIBAC S-810(NanoPi NEO x8 nodes), but differnces are exist.
In this article, I explain the differnces of building Mini-Supercomputer using NanoPi-NEO4.

![DSCF2503.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449377/508b4b28-aa8f-2eeb-5615-55cddb12e496.jpeg)


# はじめに

NanoPi-NEO4は中国のFriendlyElec社が発売している超小型サイズのLinuxコンピュータです。おなじみRaspberry Piの半分サイズの基板の上にRK3399 SOCや1000Base-T対応イーサネット、HDMIコネクタ(標準サイズ)が搭載されており、Raspberry Piのミニチュア版(ちんちくりんで可愛い)に見えますが、性能はRaspberry Pi model3B+より1枚上を行くスゴい奴です。

前回NanoPi-NEOを8台並列にしたミニスパコンを製作しましたが、このNanoPi-NEO4を並列にしたらどれくらいの性能が出るでしょうか。

そこで、本稿ではNanoPi-Neo4を3台並列にしたミニスパコンの作り方を説明します。
基本的なところは以前製作したNanoPi-NEO x8台にしたSUGIBAC S-810と大体共通ですが、若干異なる部分がありますので、その点について説明します。
なお、NanoPi-NEO x8台並列のミニスパコンの作り方についてはこちらを参照してください。

https://qiita.com/jo3vpsmochi/items/e9ae9ffa2b9fa4482095

なお、今回使用したNanoPi-NEO4はFriendlyElec社から提供していただいたものです。皆様のご厚意に感謝いたします。

# 用意したもの

次のものを用意しました。

+ NanoPi-Neo4 3台(放熱板つき)
+ 1000Base-T対応スイッチングHUB
+ 短いLANケーブル 3本
+ ジャンク箱から出てきたリレー
+ USB-Cコネクタつき電源ケーブル
+ 5V 20Aのスイッチング電源
+ USB-シリアル変換アダプタ(1.5Mbpsで通信できるもの。いざという時に使います)
+ その他、木箱や配線材料など

なお、スイッチング電源は極端な軽負荷や無負荷時に出力電圧が若干上昇するものがありますので、出力に抵抗をつないで100mA程度のブリーダ電流を流しておくようにするとよいです。

# 作り方
## OSのインストール(親機)
OSにはFriendlyCoreを使用しました。通常通りダウンロードしてきてマイクロSDカードにddか何か適当なツールでコピーします。

OSが起動したらGNU Fortranコンパイラ(gfortran)やNFS関連パッケージ、openMPIをインストールします。このへんは前と同じです。固定のIPアドレスを振るのも忘れないようにしましょう。

できたらこの状態のOSイメージを吸い出しておき、子機のマイクロSDカード作成に利用します。

## 子機側のOSインストール
先にインストールして吸い出しておいたOSイメージをマイクロSDカードにコピーして子機用のマイクロSDカードを作成します。

## 子機側OSの設定

** ここは前回と異なる部分です **

NanoPi-NEO4用のFriendlyCoreは動作していない状態(要するに別のホスト上でマウントしている状態)で設定ファイルを編集しても動作に反映されないことがわかりました。そこで、子機用のマイクロSDカードでNanoPi-NEO4を起動し、設定を行う必要があります。

なお、重要なことですが、このとき同じネットワークセグメント上に親機が起動した状態で居ると通信がうまくいきません。詳細は後述します。

### IPアドレスとMACアドレスの設定

/etc/network/interfaces ファイルを編集し、子機用のIPアドレスを設定します。
なお、前回のNanoPi-NEOの場合はこれだけで問題なかったのですが、NEO4の場合はこれだけでは複数台HUBに接続した場合に通信エラーが発生します。

手持ちの古いリピータHUBに接続した場合は正常に通信できるのですが、スイッチングHUBに接続した場合に通信できたりできなかったりします。最初はケーブルの不具合を疑いましたが、

どういうわけか親機とMACアドレスが重複している

のが原因でした。

リピータHUBの場合は全ポートにパケットを垂れ流し、受信側では自分のIPアドレス宛のパケットのみを取り込む(MACアドレスは関係なし)ので正常に通信できます。しかし、スイッチングHUBの場合はMACアドレスを見てどのポートに転送するかを決めているのでMACアドレスの重複があるとどのポートに転送してよいのかわからなくなるのが原因です。

そこで、/etc/network/interfacesファイルのeth0の項目に

hwaddress ether <子機のMACアドレス>

を追記して子機のMACアドレスを親機と重複しないように設定します。なお、このときMACアドレスは同一のネットワークセグメント上で重複していなければOKです。

### NFSの設定
前回のNanoPi-NEOのときと同じ設定にしましたが、親機側で「これこれのディレクトリをexportできない」というエラーが出てexportできませんでした。原因がよくわからないため、今回はNFSは使用せず、親機でコンパイルした実行プログラムをscpで子機にコピーすることで対応しました。

少し面倒ですが、makefileの中にscpコマンドを書いておき、make install を実行したときに子機にコピーされるようにしておきます。

### 親機の公開鍵を子機にコピーする
親機上でssh-keygenコマンドでSSHの鍵を生成します。子機にこの公開鍵をコピーします。パスフレーズなしで子機にログインできることを確認しましょう。

## 組み立て
今回も前回と同じく木箱に組み込みました。

スイッチング電源の出力電圧は若干高めに出ていることが多い(特に中古の場合、前のユーザが配線による電圧降下を見越して高めに調整していることがよくあります)ので、テスタで丁寧に5Vに合わせこみます。

なお、大出力のスイッチング電源の場合、AC100Vを投入してから5V出力が安定するまで少し時間がかかることがあり、起動不具合の原因になったりするので、スイッチング電源は常時通電状態としておき、5Vの供給はリレーで行うようにしました。

今回使用したリレーは50年ほど前の大型のもので動作時に爆発したのではないかと思うくらいの大音響がしますので、みなさんが作るときはもう少し静かなものを選んでください。

作成しておいた短いLANケーブルをスイッチングHUBに差し込み、電源を配線して完成です。

![DSCF2505.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449377/4532f781-d1e5-e5e7-de3b-1a3d4d4bc541.jpeg)

# お楽しみのベンチマークタイム

NanoPi-NEO4 x3台並列のミニスパコンがこれで完成しましたので、前に作ったNanoPi-NEO x8台並列のミニスパコンと性能を比較してみましょう。

なお、前に作ったミニスパコンはこんな恰好をしています。
![DSCF2462.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/449377/57455fc6-6df7-f223-c3e7-e53874f6967f.jpeg)

## Leibnizの公式による円周率計算対決

プログラムは次の通りです。mpiccコマンドでコンパイルします。

```leibniz_c.c
/*
	leibniz_c.c
	calculation of PI by Leibniz's formula
	(MPI version)
*/
#include <mpi.h>
#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[])
{
	long long i,j;
	int irank,numprocs;
	char hostname[MPI_MAX_PROCESSOR_NAME];
	long n=200000000;
	double x,y,sum,pi;
	double tstart,tend;
	double td;

	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
	MPI_Comm_rank(MPI_COMM_WORLD,&irank);
	if(irank==0)
	{
		printf("PROCESSORS     : %d\n",numprocs);
		printf("N              : %ld\n",n);
	}
	MPI_Bcast(&n,1,MPI_LONG,0,MPI_COMM_WORLD);

	tstart = MPI_Wtime();
	pi = 0.0;
	sum = 0.0;
	for(i=n-irank;i>0;i-=numprocs)
	{
		j = i - 1;
		if((j%2)==0) y = 1.0;
		else y = -1.0;
		x = y*4.0/(float)(2*j+1);
		sum = sum + x;
	}
	MPI_Reduce(&sum,&pi,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
	tend = MPI_Wtime();
	td = tend - tstart;

	if(irank==0)
	{
		printf("PI(Calculated) : %.30f\n",pi);
		printf("PI(M_PI)       : %.30f\n",M_PI);
		printf("Relative Error : %.30f\n",fabs(pi-M_PI));
		printf("TIME           : %9.3f\n",tend-tstart);
		printf("MFLOPS         : %9.3f\n",(double)(n*3)*1.0e-6/td);
	}

	MPI_Finalize();
}

```

実行結果です。

NanoPi-NEO x8台並列(32コア)の場合

```
pi@S810-1:/media/share$ mpiexec -np 32 --hostfile ./nodelist ./leibniz_c
PROCESSORS     : 32
N              : 200000000
PI(Calculated) : 3.141592713194430785961230867542
PI(M_PI)       : 3.141592653589793115997963468544
Relative Error : 0.000000059604637669963267398998
TIME           :     0.475
MFLOPS         :  1263.314
```

NanoPi-NEO4 x3台並列(18コア)の場合

```
pi@S820-1:~$ mpiexec -np 18 --hostfile ./nodelist ./leibniz_c
PROCESSORS     : 18
N              : 200000000
PI(Calculated) : 3.141592713194494734807449276559
PI(M_PI)       : 3.141592653589793115997963468544
Relative Error : 0.000000059604701618809485808015
TIME           :     0.275
MFLOPS         :  2181.907
```

コア数は少ないものの、2倍弱の性能を出しています。CPUコアのアーキテクチャの差でしょうか。

## FORTRANによるほこりのシミュレーション

次にランダムウォークによるほこりのシミュレーションプログラムで比較してみます。FORTRAN IVで書いてありますので、mpif77コマンドでコンパイルします。

```hokori.f
C     HOKORI
C     SIMULATES MOVEMENT OF DUST IN ROOM
      INCLUDE 'mpif.h'
      INTEGER IERR,IRANK,ISIZE,LEN
      DOUBLE PRECISION TSTART,TEND,TD,MFLOPS
      DIMENSION ISTATUS(MPI_STATUS_SIZE)
      DIMENSION ROOM(20,20)
      DIMENSION IROOM(20,20)
      DIMENSION DUST(2,10000)
      DIMENSION JROOM(20,20)
C     SETUP FOR SIMULATION
      CALL MPI_INIT(IERR)
      CALL MPI_COMM_SIZE(MPI_COMM_WORLD,ISIZE,IERR)
      CALL MPI_COMM_RANK(MPI_COMM_WORLD,IRANK,IERR)
      IF(IRANK.NE.0) GO TO 5
      READ(5,100) NSTEP,NDUST
  100 FORMAT(I8,I5)
    5 CALL MPI_BCAST(NSTEP,1,MPI_INTEGER,0,MPI_COMM_WORLD,IERR)
      CALL MPI_BCAST(NDUST,1,MPI_INTEGER,0,MPI_COMM_WORLD,IERR)
      IF(IRANK.NE.0) GO TO 90
C     STEP 1 GENERATES DUST IN ROOM
      DO 10 I=1,NDUST
      X = RAND(0)*2.0-1.0
      Y = RAND(0)*2.0-1.0
      DUST(1,I)=X
      DUST(2,I)=Y
   10 CONTINUE
C     STEP 2 GENERATES ROOM CONDX
      DO 20 I=1,20
      DO 20 J=1,20
      IF(I.LE.8.OR.I.GE.12) GO TO 21
      IF(J.LE.8.OR.J.LE.12) GO TO 21
      ROOM(I,J)=0.05
      GO TO 20
   21 IF(I.LE.5.OR.I.GE.15) GO TO 22
      IF(J.LE.5.OR.J.LE.15) GO TO 22
      ROOM(I,J)= 0.025
      GO TO 20
   22 IF(I.LE.2.OR.I.GE.18) GO TO 23
      IF(J.LE.2.OR.J.GE.18) GO TO 23
      ROOM(I,J)= 0.0125
      GO TO 20
   23 ROOM(I,J)= RAND(0)*0.01
   20 CONTINUE
   90 CALL MPI_BCAST(DUST,2*NDUST,MPI_REAL,0,MPI_COMM_WORLD,IERR)
      CALL MPI_BCAST(ROOM,20*20,MPI_REAL,0,MPI_COMM_WORLD,IERR)
C     START SIMULATION
      TSTART=MPI_WTIME()
      DO 30 I=1,NSTEP
      DO 30 N=IRANK+1,NDUST,ISIZE
      IX = IFIX(DUST(1,N)*10.0+10.0)+1
      IY = IFIX(DUST(2,N)*10.0+10.0)+1
      X = DUST(1,N)+(RAND(0)-0.5)*ROOM(IX,IY)
      Y = DUST(2,N)+(RAND(0)-0.5)*ROOM(IX,IY)
      IF(ABS(X).LE.0.95.AND.ABS(Y).LE.0.95) GO TO 35
      IF(X.LE.0.0) X= -0.99
      IF(X.GT.0.0) X= 0.99
      IF(Y.LE.0.0) Y= -0.99
      IF(Y.GT.0.0) Y = 0.99
   35 DUST(1,N)=X
      DUST(2,N)=Y
   30 CONTINUE
C     PRINT SIMULATION RESULT
      DO 40 I=1,20
      DO 40 J=1,20
      IROOM(I,J)=0
      JROOM(1,J)=0
   40 CONTINUE
      DO 50 N=IRANK+1,NDUST,ISIZE
      IX = IFIX(DUST(1,N)*10.0+10.0)+1
      IY = IFIX(DUST(2,N)*10.0+10.0)+1
      IROOM(IX,IY)=IROOM(IX,IY)+1
   50 CONTINUE
      CALL MPI_REDUCE(IROOM,JROOM,20*20,MPI_INTEGER,MPI_SUM,0,
     1MPI_COMM_WORLD,IERR)
      TEND=MPI_WTIME()
      TD=TEND-TSTART
      IF(IRANK.NE.0) GO TO 70
      MFLOPS=DBLE(FLOAT(NSTEP))*DBLE(FLOAT(NDUST))*14.0
      MFLOPS=(MFLOPS+DBLE(FLOAT(NDUST))*6.0)*1.0E-6/TD
      WRITE(6,200) NSTEP,NDUST
  200 FORMAT(1H ,8HNSTEP = ,I8,9H NDUST = ,I5)
      DO 60 I=1,20
      WRITE(6,210) (JROOM(I,J),J=1,20)
  210 FORMAT(1H ,20I3)
   60 CONTINUE
      WRITE(6,220) TD,MFLOPS
  220 FORMAT(1H ,12HTIME(SEC) : ,F8.3/1H ,12HMFLOPS    : ,F8.3)
   70 CALL MPI_FINALIZE(IERR)
      STOP
      END
```

NanoPi-NEO x8台並列(32コア)の場合

```
pi@S810-1:/media/share$ echo " 1000000 1000" | mpiexec -np 32 --hostfile ./nodel
ist ./hokori_mpi
 NSTEP =  1000000 NDUST =  1000
 216  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0222
   0 11  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  5  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 11  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0 37  1  0  1  0  0
   0  0  0  0  0  0  0  0  0  0  0 27  0  0  0  0  0  0 15  0
 256  2  0  0  1  0  0  0  0  0  0  0  0  0  0  0 17  0  0178
 TIME(SEC) :   33.598
 MFLOPS    :  416.686
```

NanoPi-NEO4 x3台並列(18コア)の場合

```
pi@S820-1:~$ echo " 1000000 1000" | mpiexec -np 18 --hostfile ./nodelist ./hokorii_mpi
 NSTEP =  1000000 NDUST =  1000
 223  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0221
   0 13  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  4  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 15  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  1  0
   0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
   0  0  0  0  0  0  0  0  0  0  0  1  0  0 43  0  0  0  0  0
   0  0  0  0  0  1  0  0  0  0  0 18  0  0  0  0  0  0  8  0
 241  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0 11  0  0199
 TIME(SEC) :   15.793
 MFLOPS    :  886.492
```

こちらは2倍強の性能となっています。

## LINPACKベンチマーク
スパコンといえばやっぱりLINPACKベンチ。これまでの計算は各コアで並列に計算を進め、最後に集計を取るものでしたので通信性能はあまり関係ありませんでしたが、LINPACKベンチは計算途中で通信を行うのでネットワークの通信性能込みの値が出ます。
結果は如何に?

NanoPi-NEO x8台並列(32コア)の場合

```
T/V                N    NB     P     Q               Time                 Gflops
--------------------------------------------------------------------------------
WR00L8L2       15000    64     4     2             346.86             6.4877e+00
HPL_pdgesv() start time Thu Mar  7 13:22:53 2019

HPL_pdgesv() end time   Thu Mar  7 13:28:40 2019

--------------------------------------------------------------------------------
||Ax-b||_oo/(eps*(||A||_oo*||x||_oo+||b||_oo)*N)=   1.05946970e-03 ...... PASSED
================================================================================
```

NanoPi-NEO x8台並列(32コア)の場合

```
T/V                N    NB     P     Q               Time                 Gflops
--------------------------------------------------------------------------------
WR00L3L6       15000    64     3     1             151.30             1.4874e+01
HPL_pdgesv() start time Fri Jun 28 10:32:42 2019

HPL_pdgesv() end time   Fri Jun 28 10:35:13 2019

--------------------------------------------------------------------------------
||Ax-b||_oo/(eps*(||A||_oo*||x||_oo+||b||_oo)*N)=   5.45981035e-03 ...... PASSED
================================================================================
```

NanoPi-NEO x8台並列の場合は約6.5GFLOPS、NanoPi-NEO4 x3台並列の場合は約15GFLOPSでした。倍以上の性能が出ます。CPUコアの演算性能もさることながら、通信ネットワークが100Base-TXから1000Base-Tに高速化されていることが大きく影響しているようです。

## (おまけ)OpenCL+MPIによる円周率計算

NanoPi-NEO4のSOCにはOpenCL対応のGPU(Mali T-864)が搭載されています。計算能力はどうでしょうか。プログラムは下記の通り、上にあげたC言語版の円周率計算部分を素朴にOpenCL化したコードです。

```leibniz_opencl_mpi.c
/*
	leibniz_opencl.c
	calculation of PI by Leibniz's formula
	(OpenCL version)
*/
#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <mpi.h>

#define MEM_SIZE (1024)
#define MAX_SOURCE_SIZE (0x100000)
#define N 200000000

float *local_buf;

int main(int argc, char *argv[])
{
	cl_device_id device_id = NULL;
	cl_context context = NULL;
	cl_command_queue command_queue = NULL;
	cl_mem memobj = NULL;
	cl_program program = NULL;
	cl_kernel kernel = NULL;
	cl_platform_id platform_id = NULL;
	cl_uint ret_num_devices;
	cl_uint ret_num_platforms;
	cl_int ret;

	clock_t	start_calc_t,end_gpu_t,end_copy_t,end_calc_t;

	long i,j;
	long n=N;
	float x,sum,pi;
	double tstart,tgpu,tcopy,tend;
	double td;

	int irank,nprocs;
	char hostname[MPI_MAX_PROCESSOR_NAME];

	MPI_Init(&argc,&argv);
	MPI_Comm_size(MPI_COMM_WORLD,&nprocs);
	MPI_Comm_rank(MPI_COMM_WORLD,&irank);
	if(irank==0)
	{
		printf("PROCESSORS     : %d\n",nprocs);
		printf("N              : %ld\n",n);
	}
	MPI_Bcast(&n,1,MPI_LONG,0,MPI_COMM_WORLD);

	/* OpenCL setup */
	char *source_str=
	"__kernel void leibniz(__global float* X,const int n,const int irank, const int nsize) \
	{ \
		long i,j,k; \
		float x,y; \
		int id= get_global_id(0); \
		int isize = get_global_size(0); \
		for(i=n-(irank*isize+id);i>0;i-=(isize*nsize)) \
		{ \
			j = i - 1; \
			if((j%2)==0) y = 1.0; \
			else y = -1.0; \
			x = y*4.0/(float)(2*j+1); \
			X[id] = X[id] + x; \
		} \
	}";
	size_t source_size = strlen(source_str);

	ret = clGetPlatformIDs(1,&platform_id, &ret_num_platforms);
	if(ret != CL_SUCCESS)
	{
		printf("Error : clGetPlatformIDs()\n");
		return -1;
	}
	ret = clGetDeviceIDs(platform_id,CL_DEVICE_TYPE_DEFAULT,1,
		&device_id,&ret_num_devices);
	if(ret != CL_SUCCESS)
	{
		printf("Error : clGetDeviceIDs()\n");
		return -1;
	}
	context = clCreateContext(NULL,1,&device_id,NULL,NULL,&ret);
	command_queue = clCreateCommandQueue(context,device_id,0,&ret);
	memobj = clCreateBuffer(context,CL_MEM_ALLOC_HOST_PTR,
		MEM_SIZE*sizeof(float),NULL,&ret);
	program = clCreateProgramWithSource(context,1,
		(const char **)&source_str,(const size_t *)&source_size,&ret);
	ret = clBuildProgram(program, 1, &device_id, NULL, NULL, NULL);
	if(ret != CL_SUCCESS)
	{
		size_t len;
		char buffer[2048];
		printf("Error : clBuildProgram()\n");
		clGetProgramBuildInfo(program,device_id,CL_PROGRAM_BUILD_LOG,
			sizeof(buffer),buffer, &len);
		printf("\t%s\n",buffer);
		exit(1);
	}
	kernel = clCreateKernel(program, "leibniz",&ret);
	if(ret != CL_SUCCESS)
	{
		printf("Error : clCreateKernel()\n");
		exit(1);
	}
	ret = clSetKernelArg(kernel,0,sizeof(cl_mem),(void *)&memobj);
	ret |= clSetKernelArg(kernel,1,sizeof(int),(void *)&n);
	ret |= clSetKernelArg(kernel,2,sizeof(int),(void *)&irank);
	ret |= clSetKernelArg(kernel,3,sizeof(int),(void *)&nprocs);
	if(ret != CL_SUCCESS)
	{
		printf("Error : clSetKernelArg()\n");
		exit(1);
	}
	/* execute kernel */
	tstart = MPI_Wtime();
	size_t globalWorkSize[] = {MEM_SIZE};
	size_t localWorkSize[] = {1};
	ret = clEnqueueNDRangeKernel(command_queue,kernel,1,NULL,
		globalWorkSize,localWorkSize,0,NULL,NULL);
	if(ret != CL_SUCCESS)
	{
		printf("Error : executing kernel\n");
		exit(1);
	}
	/* reading result */
	local_buf = (float *)clEnqueueMapBuffer(command_queue,memobj,CL_TRUE,CL_MAP_READ,
		0,MEM_SIZE*sizeof(float),0,NULL,NULL,&ret);
	
	pi = 0.0;
	sum = 0.0;
#pragma omp parallel for reduction(+ : sum)
	for(i=0;i<MEM_SIZE;i++)
	{
		sum = sum + *(local_buf+i);
	}
	MPI_Reduce(&sum,&pi,1,MPI_FLOAT,MPI_SUM,0,MPI_COMM_WORLD);
	tend = MPI_Wtime();
	td = tend - tstart;
	if(irank==0)
	{
		printf("PI(Calculated) : %.30f\n",pi);
		printf("PI(M_PI)       : %.30f\n",M_PI);
		printf("Relative Error : %.30f\n",fabs(pi-M_PI));
		printf("TIME           : %9.3f\n",tend-tstart);
		printf("MFLOPS         : %9.3f\n",(double)(n*3)*1.0e-6/td);
	}
	/* Finalization */
	ret = clFlush(command_queue);
	ret = clFinish(command_queue);
	ret = clReleaseKernel(kernel);
	ret = clReleaseProgram(program);
	ret = clReleaseMemObject(memobj);
	ret = clReleaseCommandQueue(command_queue);
	ret = clReleaseContext(context);
	MPI_Finalize();
}

```

計算結果は次の通りです。

```
pi@S820-1:~$ export OMP_NUM_THREADS=1;mpiexec -np 18 --hostfile ./nodelist ./leibbniz_opencl_mpi
PROCESSORS     : 18
N              : 200000000
PI(Calculated) : 3.141592979431152343750000000000
PI(M_PI)       : 3.141592653589793115997963468544
Relative Error : 0.000000325841359227752036531456
TIME           :     1.168
MFLOPS         :   513.817
pi@S820-1:~$ export OMP_NUM_THREADS=6;mpiexec -np 18 --hostfile ./nodelist ./leibbniz_opencl_mpi
PROCESSORS     : 18
N              : 200000000
PI(Calculated) : 3.141592741012573242187500000000
PI(M_PI)       : 3.141592653589793115997963468544
Relative Error : 0.000000087422780126189536531456
TIME           :     1.142
MFLOPS         :   525.483
pi@S820-1:~$ export OMP_NUM_THREADS=1;mpiexec -H S820-1,S820-2,S820-3 ./leibniz_oopencl_mpi
PROCESSORS     : 3
N              : 200000000
PI(Calculated) : 3.141593694686889648437500000000
PI(M_PI)       : 3.141592653589793115997963468544
Relative Error : 0.000001041097096532439536531456
TIME           :     1.409
MFLOPS         :   425.933
pi@S820-1:~$ export OMP_NUM_THREADS=6;mpiexec -H S820-1,S820-2,S820-3 ./leibniz_oopencl_mpi
PROCESSORS     : 3
N              : 200000000
PI(Calculated) : 3.141592264175415039062500000000
PI(M_PI)       : 3.141592653589793115997963468544
Relative Error : 0.000000389414378076935463468544
TIME           :     1.229
MFLOPS         :   488.252
```

素朴な並列化がいけなかったのか、C言語によるフラットMPI版の約1/4の計算速度でした。

# まとめ

NanoPi-NEO4でも手軽に小さなスパコンが組めることがわかりました。計算能力は3台並列でもNanoPi-NEO x8台並列の2倍程度出ますので、作ってみてはいかがでしょうか。