pythonでraspberry pi3のCPU性能を引き出してみた

LINEで送る
Pocket

がじです。

今更ですが、raspberry pi3でどこまでの処理が期待できるのかを調べています。

きっかけはRaspberry Piでクラスタコンピューティングの勉強がしたくてGoogle先生に問い合わせていたところ「RPi – Raspberry Pi で High Performance Computing! スパコンを作る方法。」という記事を見たことにあります。

※2017/11/1 22:35追記。初出時に上記参考ブログのURLが判らなくて書いてませんでしたが、見つかったので追記しました。

とはいってもいきなり複数台のRasPiを用意するのもいろいろ危険なので単体でどの程度の計算パワーを持っているかを調べてみるところから始めようと思いました。

というわけで今回はpythonでraspberry pi3 の計算処理を実行させてみました。特に計算高速化のライブラリは使用せず、スレッド/プロセス処理で解決させています。

___スポンサードリンク

Raspberry Pi 3 MODEL B 【RS正規流通品】

新品価格
¥5,000から
(2017/11/1 21:47時点)


___

私がだいぶ以前にJavaとC言語の比較に使った方法を思い出しつつ、Pythonのパフォーマンスを見ることにしました。(このブログでは初出です。当時のソースコードもバイナリもすでにどこにあるかわからないので記憶で書いている部分がありますのでご注意ください。)

f(x) = 4 / (1 + x^2) という関数の[0,1]区間の積分値は円周率πの近似値になることが知られています。
CPUで計算させる場合、[0,1]区間をn個に分割し、xnの時のf(xn)の値を合計した値を持ってπの近似値を計算します。

n区間を細かくすれば細かくするほど精度は上がりますが、処理時間が増えます。(数学的には分割数は無限大です)
ここではn=1000000(1000万)で計算することにしました。

simple-py.py

# -*- coding: utf-8 -*-
import time

def f(x):
  return 4 / (1.0 + x**2)

start_t = time.time()
n = 10000000
sum = 0
step = 1.0 / n

for i in range(0, n):
  x = (i + 0.5) * step
  sum += f(x)

pi = sum * step

end_t = time.time()

elapsed_t = end_t - start_t

print("pi = " , str(pi))
print("execute time:", str(elapsed_t))

これをraspberry pi3上で実行してみます。

$ python simple-py.py
(‘pi = ‘, ‘3.14159265359’)
(‘execute time:’, ‘38.0594520569’)

しまった、python3で記述したつもりが、raspberry py3のデフォルトではpython 2.7が標準のため、python2.7上で実行されてしまいました。

やり直し

$ python3 simple-py.py 
pi =  3.141592653589731
execute time: 48.527559995651245

python3のほうが若干処理時間が伸びていますが、精度は上がっています。ライブラリの構成の違いの影響でしょう。

raspberry pi3は4コアCPUです。
この処理を実行する時に、raspberry pi3のCPUの1つのコアは100%稼働していますが、のこりの3つのコアはほぼ何もしていません。

残りのコアにも少し処理を分散させるためにスレッドクラスを2つ作成し、処理の前半と後半をそれぞれのスレッドで分割処理させて最後に足し算する処理を組んでみます。

multi_py01.py

# -*- coding: utf-8 -*-
import threading
import time
import datetime

def f(x):
    return 4 / (1.0 + x**2)

class TestThread(threading.Thread):
    def __init__(self, st, ed, step):
      super(TestThread, self).__init__()
      self.st=st
      self.ed=ed
      self.step = step
      self.return_value = None

    def run(self):
      sum = 0
      for i in range(self.st, self.ed):
        x = (i + 0.5) * self.step
        sum += f(x)
     self.return_value = sum * step

    def get_value(self):
      return self.return_value

if __name__ == "__main__":
  start_t = time.time()
  pi = 0
  n = 10000000
  step = 1.0 / n
  sub_t1 = TestThread(0, int(n/2), step)
  sub_t2 = TestThread(int(n/2), n, step)
  sub_t1.start()
  sub_t2.start()
  # sub_t.join()
  while(sub_t1.is_alive() or sub_t2.is_alive()):
    time.sleep(1)
  pi = sub_t1.return_value + sub_t2.return_value
  end_t = time.time()
  elapsed_t = end_t - start_t
  print("pi = " , pi)
  print("execute time = ", str(elapsed_t))

結構適当です。(^_^;)
適当なのには一応理由がありまして、Python(だけはありませんが)等のスクリプト言語の多くでは、メモリ共有型のThread処理では高速化は期待できないと言われているためです。

ただ、本当に全然高速化できないのか年のために確認したかったのです。スレッド処理待ちループの中でsleep(1)とかしてるのがアレですが、ご容赦願います。

それでは実験

$ python3 multi_pi01.py 
pi =  3.141592653589923
execute time =  47.22843599319458

うん、誤差の範囲ですね。CPUモニターでCPUの稼働率を見ても2つのCPUで50%/50%となっていて、CPUは2つ使うけどスレッドの切り替えで50%のリソースを食うので1CPU 100%とほとんど差がないわけです。

ではメモリを共有しないProcess処理で計算させることで4コアリソースをフルに使いきってみることにします。

参考にしたサイトはこちら

multi_pi02.py

# -*- coding:utf-8 -*-

import time
import datetime

from multiprocessing import Pool
from collections import Counter

def f(x):
    return 4 / ( 1.0 + x**2 )

# multiprocess
def multi_func(st_x, st_y, step):
    sum = 0
    for i in range(int(st_x), int(st_y)):
        x = (i + 0.5) * step
        sum += f(x)
    # print(sum)
    return sum

def wrapper(args):
    return multi_func(*args)

def multi_process(sampleList):
    # Procecc Num:8
    p = Pool(4)
    output = p.map(wrapper, sampleList)
    # process End
    p.close()
    return output

if __name__=="__main__":

    # n回の処理を並列処理
    num = 10000000
    p_num = 4
    pi = 0
    step = 1.0 / num
    sumpleList=[]

    start_t = time.time()
    sampleList= [(i*num/4, (i+1)*num/4, step ) for i in range(p_num)]

    output = multi_process(sampleList)
    end_t = time.time()
    for i in range(len(output)):
        pi += output[i]
    pi *= step

    elapsed_t = end_t - start_t
    print("pi = ", str(pi))
    print("execut time:", str(elapsed_t))

ほぼマネじゃんというツッコミはなしでお願いします。
実行結果はこちら

$ python3 multi_pi02.py 
pi =  3.1415926535896697
execut time: 12.046535968780518

ほぼCPU稼働率は4つのCPUですべて100%、処理時間は1/4程度になりました。妥当ですね。
数値が若干違うのは丸め誤差の影響が出てるのかな?よくわかりません<ヲイ

実際の実行画面を載せておきます。

___スポンサードリンク

___

なお、Corei7 4770Tで同じ処理を実行させたら1コアで3秒、4プロセス実行で0.7秒程度で処理が終わりました。わかっていたけどCorei系の性能って高いのね、と思いました。

LINEで送る
Pocket