f2pyを使ってFortranコードをPythonで実行(ベンチマーク)

Fortranのコードをpythonに結び付けるにはf2pyツールが利用できる。そこで、f2pyを使用した場合の計算の実行速度を測定する。

  • 実行手順
    • 計算内容は、tanhやexpで表現した数式の足し算を1億回行うだけの単純なもの。
    • Fortranコードの計算時間を測定。
    • Pythonコードの計算時間を測定。
    • Fortranサブルーチンを読込むPythonコードの計算時間を測定。
  • テスト環境

まず、以下のFortranコードを実行する(cacl_test.f)。gfortranでコンパイル(オプションは-O3)。

c calc_test.f
c
      program calc_test
      implicit none
c
      integer i, numberIteration
      real*8 xValueMin, xValueMax, dxValue, xValue
      real*8 sum
      real   START_TIME, END_TIME
c
      call CPU_TIME(START_TIME)
c
      sum = 0.0
      numberIteration = 100000000
      xValueMin = -10.0
      xValueMax =  10.0
      dxValue = (xValueMax - xValueMin)/numberIteration
c
      do i = 1, numberIteration
        xValue = xValueMin + dxValue*(i-1)
        sum = sum + 2.5*dtanh(xValue)*dexp(-0.33*dabs(xValue))
      end do
      write(*,*) '# ANSWER= ',sum
c
      call CPU_TIME(END_TIME)
      write(*,*) '# TIME= ',END_TIME - START_TIME, ' (sec)'
c
      stop
      end program calc_test

その結果は、

 # ANSWER=  -9.22079037357930870E-002
 # TIME=    10.120632      (sec)

となる。

次にpythonだけで同じコードを書く(calc_test.py)。

# calc_test.py
#
from math import *
import time

startTime = float(time.time())
sum = 0.0
numberIteration = 100000000
xValueMin = -10.0
xValueMax =  10.0
dxValue = (xValueMax - xValueMin)/numberIteration
#
for i in xrange(numberIteration):
    xValue = xValueMin + dxValue*(i)
    sum += 2.5*tanh(xValue)*exp(-0.33*abs(xValue))
#
print '# ANSWER= ',sum
endTime = float(time.time())
print '# TIME= %f (sec)' % (endTime-startTime)

その結果は、

# ANSWER=  -0.0922079112232
# TIME= 122.246774 (sec)

となる。Fortranの結果と比べて計算結果が若干異なる(小数点8位以下)のが気になるが、ここではその点は深く追求しない。

次に、pythonコードの中でforループで足し算をしていた部分だけをFortranのサブルーチンとして独立させる。次のファイルcalc_sub.fを作成する。

      subroutine calc(xValueMin,xValueMax,numIter,sum)
      implicit none
c
      integer i, numIter
      real*8 xValueMin, xValueMax, dxValue, xValue
      real*8 sum
      sum = 0.0
c
      dxValue = (xValueMax - xValueMin)/numIter
c
      do i = 1, numIter
        xValue = xValueMin + dxValue*(i-1)
        sum = sum + 2.5*dtanh(xValue)*dexp(-0.33*dabs(xValue))
      end do
      write(*,*) '# ANSWER=',sum
c
      return
      end

これをモジュール化するのにコマンドf2pyを用いる。次のコマンドを実行。

$ f2py -m calc_sub -c calc_sub.f

するとcalc_sub.soが作られる。このとき特にコンパイラを指定しないが、勝手に見つけてくれてオプションも付けてくれるみたい。コンパイラを指定するときは、オプションで--fcompiler=gnuのようにする。利用可能なコンパイラのリストは以下のコマンドで確認できる。

$ f2py -c --help-fcompiler

calc_sub.soをpythonコードでimportする。コードは次のようになる(calc_sub.py)。

# calc_sub.py
#
from calc_sub import calc
import time

startTime = float(time.time())
numberIteration = 100000000
xValueMin = -10.0
xValueMax =  10.0
sum = 0.0
#
calc(xValueMin, xValueMax, numberIteration, sum)
#
endTime = float(time.time())
print '# TIME= %f (sec)' % (endTime-startTime)

このコードを実行すると結果は、

# ANSWER= -9.22079037357930870E-002
# TIME= 10.963634 (sec)

となる。この単純な例だと当たり前の結果だが、やっぱり速い。