Jetson TX1におけるGPU性能調査 その1

#はじめに
Jetson TX1のGPUを使用した効能を調査します。
第一回として、Pythonでもお手軽にGPU環境が使用できるCuPyを使用してみました。
(C/C++よりPythonの方がサクッと試せそうだったのでPythonを選択しています)
(ゴリゴリにCUDAやGPGPUを活用したい場合はC/C++を選択すべきかも?)

#CuPyとは
Chainerをインストールすることで使用できるPython用ライブラリ。
NumPyと同じ構文・感覚で使用でき、うまく使えばNumPyとCuPy配列を手軽に切り替えられる。

```switch_numpy_cupy.py
import numpy
import cupy

#xp = numpy
xp = cupy

a = xp.arange( 25 ).reshape( 5 , 5 )
a = a.dot( a )
```

参考リンク: [Preferred Networks社のスライドシェア](http://www.slideshare.net/unnonouno/chainer-cupy)
参考リンク: [ChainerとCuPyについて] (http://sora-sakaki.hatenablog.com/entry/2015/09/23/232409) 

#GPUの効能調査
CPU代表としてNumPyに、GPU代表としてCuPyに頑張ってもらいます。

##比較方法
 * 配列の内積を求めるプログラムで計算時間を比較する。
  * 配列の準備やGPUのデバイスオープンの時間は除き、純粋に内積の計算時間のみ比較する。
 * 下記プログラム内の「xp = ○○」をコメントアウトで切り替えることで、CPUのみとGPUを使用した場合を切り替える 
 * 下記プログラム内の「N」を10～1000変えてみて、CPUのみとGPUを使用した場合の計算時間を求める
 * ターミナルにて「python gpu_test.py」を実行し処理時間を出力させる

```gpu_test.py
import time
import numpy
import cupy

#xp = numpy
xp = cupy

#配列の次元
N = 10
N2 = N * N

#配列のセット
a = xp.arange( N2 ).reshape( N , N )
start = time.time()
#内積の計算
a = a.dot( a )
end = time.time()
#内積の計算時間を出力する
print ( end - start )
```

##結果
* 500×500配列の内積計算まではCPUの方が高速、700×700配列以降はGPUの圧勝
* 配列の次元が増加するに連れ、
 * CPUのみの場合はは指数関数的に計算時間が伸びている
 * GPUを使用すると計算時間は微増するだけ

結果をまとめた表とグラフ
![numpy_cupy.png](https://qiita-image-store.s3.amazonaws.com/0/141396/cc224a15-e22a-cacd-34c3-ddf84aa63240.png)　![numpy_cupy_1.png](https://qiita-image-store.s3.amazonaws.com/0/141396/d14f236f-b86c-0be6-2458-1587cd553906.png)

横軸の範囲を0～1200 -> 0～700、縦軸の範囲を0～40 -> 0～3.5に変更したグラフ
![numpy_cupy_2.png](https://qiita-image-store.s3.amazonaws.com/0/141396/e368a71c-40f1-30ab-0bfb-dd7093f10c42.png)

#GPUの効能調査(追加確認)
chachayさんのナイスなコメントをいただきましたので、CuPyに関して追加で確認を実施しました。
(chachayさん、ありがとうございますm(_ _)m)
CuPyで内積を計算する前に、CuPyを用いた適当な計算を行うことにしました。

##追加確認の内容
追加確認では下記の2種のプログラムを使用します。

* gpu_test_1.py: 
 * 上のテストとやってることは変わらない
 * 1回プログラムを実行(ターミナルで「python gpu_test_1.py」)すればN=10～1000まで全部計算してくれる
* gpu_test_2.py:
 * 計算時間を計測したい内積計算の前にCuPyを用いた適当な計算を行い、CUDAのコンパイルを先に完了させる
 * 1回プログラムを実行(ターミナルで「python gpu_test_2.py」)すればN=10～1000まで全部計算してくれる

```gpu_test_1.py
import time
import numpy
import cupy

#xp = numpy
xp = cupy

N = [10,100,200,300,400,500,600,700,800,900,1000]

#if xp == cupy:	
#	z = xp.arange(4).reshape( 2 , 2 )
#	z.dot( z )

for N in N:
	N2 = N * N
	a = xp.arange(N2).reshape(N, N)
	start = time.time()
	a.dot(a)
	end = time.time()
	print str(N) + " * " + str(N) + " => " + str(end - start)
```

```gpu_test_2.py
import time
import numpy
import cupy


#xp = numpy
xp = cupy

N = [10,100,200,300,400,500,600,700,800,900,1000]

# CuPyを用いた適当な計算を実行し、CUDAのコンパイルを先に完了させる
if xp == cupy:	
	z = xp.arange(4).reshape( 2 , 2 )
	z.dot( z )

for N in N:
	N2 = N * N
	a = xp.arange(N2).reshape(N, N)
	start = time.time()
	a.dot(a)
	end = time.time()
	print str(N) + " * " + str(N) + " => " + str(end - start)
```

##追加確認の結果
先にCUDAのコンパイルを完了させるのとさせないのでは、1回目の内積計算で大きな差(約0.6sec)が出ました。

結果をまとめた表とグラフ
![numpy_cupy.png](https://qiita-image-store.s3.amazonaws.com/0/141396/6e5092d2-9750-a428-a523-4f37529a7bba.png)　![numpy_cupy_3.png](https://qiita-image-store.s3.amazonaws.com/0/141396/6a50422e-0b3f-53cc-77b3-836d5df01572.png)
縦軸の範囲を0～0.7 -> 0～0.18に変更したグラフ
![numpy_cupy_4.png](https://qiita-image-store.s3.amazonaws.com/0/141396/65f2802f-0d89-a2bb-c6f8-bd90d38be6f3.png)

#CPU(NumPy)とGPU(CuPy)の計算時間 決定版
※**Jetson TX1の場合の計算時間です**
(他のGPUボード等では結果に差が出る可能性があります)

* 10×10配列など配列の次元が小さい場合はCPUの方が高速だが、200×200配列以降はGPUの方が速くなる
* 配列の次元が増加するに連れ、
 * CPUのみの場合はは指数関数的に計算時間が伸びている
 * GPUを使用すると計算時間は微増するだけ

結果をまとめた表とグラフ
![numpy_cupy.png](https://qiita-image-store.s3.amazonaws.com/0/141396/f484fe14-682f-dac1-681c-b8b215648194.png)　![numpy_cupy_5.png](https://qiita-image-store.s3.amazonaws.com/0/141396/95f9576b-344f-5bc3-4310-4065924e381e.png)

横軸の範囲を0～1200 -> 0～700、縦軸の範囲を0～40 -> 0～0.1に変更したグラフ
![numpy_cupy_6.png](https://qiita-image-store.s3.amazonaws.com/0/141396/15fa81c4-048c-677a-bd7a-60c7f43db6c0.png)



#終わりに
~~配列の次元が小さく並列度が小さい場合は、GPUの効果があまり感じられないことが分かりました(いまさら感)。
ただ、内積計算だけで言えば、700×700配列を使用しなければGPUの効果が得られないのは意外でした。
CPUからGPUへのメモリアクセスによる遅延だけが問題なのか、それ以外に問題があるのか深堀したいですね。~~

(2016/09/23追記)
Jetson TX1において、内積の計算は200×200配列以降でGPUの方が速く計算できることがわかりました。
関数自体を並列化するなどすれば、配列の次元が小さくてもGPUの方が速く計算できそうな気がします。
(CuPyで関数の並列化できるのかなど、今後の調査課題も見つかりました)
また、追加確認の結果、最初にCuPy実行時にCUDAのコンパイルが実行されるということがわかりました。

コメントでご指摘をくださったchachayさん、ありがとうございます！