2013年08月10日

[Homebrew] SciPyにOpenBLASを組込んで高速化する

05/28の記事で、OpenCV + Python環境を構築する過程でPython仮想環境にNumPyとSciPyを組み込んだ。どちらもPythonでプログラム開発をやるには必須のライブラリだが、この2つについて調べるうちに、OpenBLASというオープンソースの線形計算ライブラリが存在し、このライブラリを組み込んでビルドするとNumPyとSciPyを高速化できるらしいことを知った。これにトライしてみたら成功したので、その作業記録を書いておく。

■OpenBLASを組み込んだNumPyとSciPyのビルド


すでにNumPyとSciPyをインストール済みだったので、まずは、「pip」コマンドを使ってこの2つのライブラリをアンイストールした。
% pip uninstall numpy
% pip uninstall scipy
% pip list
distribute (0.6.40)
stevedore (0.10)
virtualenv (1.9.1)
virtualenv-clone (0.2.4)
virtualenvwrapper (4.1.1)
wsgiref (0.1.2)

続いて、Homebrewに新しいGitHubリポジトリを追加した。
% brew tap samueljohn/python

OpenBLASについてググっているうちに、OpenBLASを組み込んでNumPyとSciPyをビルドできるフォーミュラ定義ファイルがこのsamueljohn/pythonリポジトリに存在しているのを見つけた。「pip install --no-install | --no-download PACKAGE」コマンドを使って、手動でコンフィグレーション定義に変更を加えながらOpenBLASを組み込んでビルドする方法もある。実際にこの方法も試してみたが、かなり面倒な手順だったので、Pythonライブラリのビルドに慣れていない人には勧められない。上のリポジトリを利用すれば、簡単にOpenBLASを組み込むことができる。このリポジトリを追加すると、Homebrewを使ってNumPyとSciPyをPython環境へインストールできるようになる(この2つ以外に、PillowやmatplotlibなどのいくつかのPythonライブラリのフォーミュラも追加される)。
% brew info numpy
numpy: stable 1.7.1, HEAD
http://numpy.scipy.org
Not installed
From: https://github.com/samueljohn/homebrew-python/commits/master/numpy.rb
==> Dependencies
Required: homebrew/science/suite-sparse
Optional: homebrew/science/openblas
==> Options
--with-openblas
Use openBLAS (slower for LAPACK functions) instead of Apple's Accelerate Framework
--with-python3
Build with python3 support
--without-python
Build without python support
==> Caveats
Numpy ignores the `FC` env var and looks for gfortran during build.
% brew info scipy
scipy: stable 0.12.0, HEAD
http://www.scipy.org
Not installed
From: https://github.com/samueljohn/homebrew-python/commits/master/scipy.rb
==> Dependencies
Build: swig
Required: numpy
Optional: homebrew/science/openblas
==> Options
--with-openblas
Use openBLAS (slower for LAPACK functions) instead of Apple's Accelerate Framework
--with-python3
Build with python3 support
--without-python
Build without python support

上のNumPyのフォーミュラ情報から、このNumPyはsuite-sparseというフォーミュラに依存していることが判る。SuiteSparseというのはCやFortran、MATLAB 用の疎行列演算ライブラリの詰め合わせパッケージ。suite-sparseとopenblasはいずれもhomebrew/scienceリポジトリに所属しているフォーミュラだ。したがって、NumPyやSciPyにこれらを組み込むには、あらかじめ下のコマンドによってhomebrew/scienceリポジトリをHomebrewに登録しておかなければならない。
% brew tap homebrew/science

OpenBLASを有効にしてNumPyとSciPyをインストールするには、次のコマンドを実行すれば良い。
% brew install numpy --with-openblas
% brew install scipy --with-openblas

ただし、先にnose(Python用単体テストフレームワーク)ライブラリをインストールしておく必要がある。
% pip install nose

numpyフォーミュラがnoseライブラリを要求してくるからだ。Python環境にnoseが入っていない状態でnumpyをインストールしようとすると、下のようなエラーメッセージが表示される。
% brew install numpy --with-openblas
numpy: Unsatisfied dependency: nose
Brewed Python cannot `import nose`. Install with:
pip-2.7 install nose
Error: An unsatisfied requirement failed this build.

numpyとscipyフォーミュラをインストールすると、依存関係によって、suite-sparse, tbb, swig, pcre, openblas(「--with-openblas」オプションを指定した場合のみ)も一緒にインストールされる。

上の操作を行った後で、numpyとscipyライブラリがPython環境にちゃんと入っているか確認してみた。
% pip list
distribute (0.6.40)
nose (1.3.0)
numpy (1.7.1)
scipy (0.12.0)
stevedore (0.10)
virtualenv (1.9.1)
virtualenv-clone (0.2.4)
virtualenvwrapper (4.1.1)
wsgiref (0.1.2)


■NumPyとSciPyの動作速度計測


OpenBLASを組み込んだNumPyとSciPyがどれ位高速になっているか確認してみた。動作速度の計測には、下のGitHub Gistページに掲載されている2つのPythonプログラムを利用させてもらった。

 Testing numpy and scipy setups

OpenBLASを組み込む前のNumPy/SciPyの動作速度

$ python test_numpy.py
FAST BLAS
version: 1.7.1
maxint: 9223372036854775807

dot: 0.130087995529 sec
% python test_scipy.py
cholesky: 0.0464385986328 sec
svd: 1.35554437637 sec

OpenBLASを組み込んだ後のNumPy/SciPyの動作速度

% python test_numpy.py
slow blas
version: 1.7.1
maxint: 9223372036854775807

dot: 1.26860480309 sec
% python test_scipy.py
cholesky: 0.0384438037872 sec
svd: 1.16273679733 sec

上の動作速度計測を行った環境は次のとおり。

 PC:MacBook Air 11-inch(Mid 2011)
 ハード仕様:Intel Core i7 1.8GHz(Sandy Bridge) HyperThreading 4コア メモリ 4GB
 コンパイラ:Clang 3.1 build 318(Xcode 4.3.3 + Command Line Tools )
 実行環境:Python 2.7.4
 計測対象:NumPy 1.7.1, SciPy 0.12.0

上の2つのケースの計測値を比較すると、次のような非常に興味深い結果になった。
 NumPy OpenBLASを組み込む前の方が約10倍速い
 SciPy OpenBLASを組み込んだ後の方が若干速い

ググっていたときに、OpenBLASを組み込んだらNumPy/SciPyが2〜3倍速くなったいう情報をいくつか見かけたが、期待していたほど速くなっていない。これらの情報は多分Linux環境での話なんだろう。しかも、NumPyではOpenBLASを組み込む前の方がずっと高速だ。OpenBLASを組み込む前のNumPy/SciPyには、Apple Accelerate Frameworkに含まれる線形計算ライブラリが使われている。これはXcodeのコンパイラに付属しているApple製のライブラリで、CPUに内蔵されている計算支援拡張命令(Intel x86ならSSE)を利用して動作するようになっている。上のような結果になる理由は、このApple製のライブラリが相当優秀だからではないかと推測できる。この結果を受けて、OpenBLASの採用について次のようにすることに決めた。
 NumPy → OpenBLASを組み込まない
 SciPy → OpenBLASを組み込む

という訳で、NumPyとSciPyのインストールを再度やり直した。
% brew uninstall scipy
% brew uninstall numpy
% brew install numpy
% brew install scipy --with-openblas

SciPyも一旦アンイストールしたのは、このフォーミュラがNumPyに依存しているからだ。

■NumPyとSciPyの動作テスト


以上でOpenBLASを組み込んだNumPyとSciPyのインストールは終了だが、せっかくnoseを入れたので、noseを使ったNumPy/SciPyの動作テストもやってみた。Pythonライブラリの動作テストを簡単に行うことができるので、noseはとても有益なライブラリだ。

先に、OpenBLASを組み込む前、つまり「pip」コマンドを使ってオプション指定なしでNumPy/SciPyをインストールした場合の動作テストの結果を載せておく。
OpenBLASを組み込む前のNumPyの動作テスト

% python
Python 2.7.4 (default, Jul 22 2013, 15:21:29)
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> numpy.show_config()
lapack_opt_info:
extra_link_args = ['-Wl,-framework', '-Wl,Accelerate']
extra_compile_args = ['-msse3']
define_macros = [('NO_ATLAS_INFO', 3)]
blas_opt_info:
extra_link_args = ['-Wl,-framework', '-Wl,Accelerate']
extra_compile_args = ['-msse3', '-I/System/Library/Frameworks/vecLib.framework/Headers']
define_macros = [('NO_ATLAS_INFO', 3)]
>>> numpy.test()
Running unit tests for numpy
NumPy version 1.7.1
NumPy is installed in /usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy
Python version 2.7.4 (default, Jul 22 2013, 15:21:29) [GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)]
nose version 1.3.0
.........................S.....................................................................................................
...............................................................................................................................
...............................................................................................................................

.... ....
.... ....

..........................................................................K....................................................
----------------------------------------------------------------------
Ran 4790 tests in 22.075s

OK (KNOWNFAIL=5, SKIP=6)
<nose.result.TextTestResult run=4790 errors=0 failures=0>
>>> quit()

OpenBLASを組み込む前のSciPyの動作テスト

% python
Python 2.7.4 (default, Jul 22 2013, 15:21:29)
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import scipy
>>> scipy.show_config()
umfpack_info:
NOT AVAILABLE
lapack_opt_info:
extra_link_args = ['-Wl,-framework', '-Wl,Accelerate']
extra_compile_args = ['-msse3']
define_macros = [('NO_ATLAS_INFO', 3)]
blas_opt_info:
extra_link_args = ['-Wl,-framework', '-Wl,Accelerate']
extra_compile_args = ['-msse3', '-I/System/Library/Frameworks/vecLib.framework/Headers']
define_macros = [('NO_ATLAS_INFO', 3)]
>>> scipy.test()
Running unit tests for scipy
NumPy version 1.7.1
NumPy is installed in /usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy
SciPy version 0.12.0
SciPy is installed in /usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/scipy
Python version 2.7.4 (default, Jul 22 2013, 15:21:29) [GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)]
nose version 1.3.0
/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy/lib/utils.py:139: DeprecationWarning: `scipy.lib.blas` is deprecated, use `scipy.linalg.blas` instead!
warnings.warn(depdoc, DeprecationWarning)
/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy/lib/utils.py:139: DeprecationWarning: `scipy.lib.lapack` is deprecated, use `scipy.linalg.lapack` instead!
warnings.warn(depdoc, DeprecationWarning)
...............................................................................................................................
...............................................................................................K...............................
...............................................................................K...............................................

.... ....
.... ....
.... ....
.... ....

======================================================================
FAIL: test_arpack.test_symmetric_modes(True, <gen-symmetric>, 'd', 2, 'SA', None, 0.5, <function asarray at 0x1031d5c08>, None, 'cayley')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
self.test(*self.arg)
File "/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/scipy/sparse/linalg/eigen/arpack/tests/test_arpack.py", line 259, in eval_evec
assert_allclose(LHS, RHS, rtol=rtol, atol=atol, err_msg=err)
File "/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy/testing/utils.py", line 1179, in assert_allclose
verbose=verbose, header=header)
File "/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy/testing/utils.py", line 645, in assert_array_compare
raise AssertionError(msg)
AssertionError:
Not equal to tolerance rtol=4.44089e-13, atol=4.44089e-13
error for eigsh:general, typ=d, which=SA, sigma=0.5, mattype=asarray, OPpart=None, mode=cayley
(mismatch 100.0%)
x: array([[-0.36892684, -0.01935691],
[-0.26850996, -0.11053158],
[-0.40976156, -0.13223572],...
y: array([[-0.43633077, -0.01935691],
[-0.25161386, -0.11053158],
[-0.36756684, -0.13223572],...

----------------------------------------------------------------------
Ran 6134 tests in 76.404s

FAILED (KNOWNFAIL=15, SKIP=47, errors=1, failures=74)
<nose.result.TextTestResult run=6134 errors=1 failures=74>
>>> quit()

NumPyの結果は「OK」だが、SciPyの方は「FAILED」になっていた。

続いて、OpenBLASを組み込んだ後のNumPy/SciPyの動作テスト結果を示す。
OpenBLASを組み込んだ後のNumPyの動作テスト

% python
Python 2.7.4 (default, Jul 22 2013, 15:21:29)
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy
>>> numpy.show_config()
blas_info:
libraries = ['openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
lapack_info:
libraries = ['openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
atlas_threads_info:
NOT AVAILABLE
blas_opt_info:
libraries = ['openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
define_macros = [('NO_ATLAS_INFO', 1)]
atlas_blas_threads_info:
NOT AVAILABLE
lapack_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
define_macros = [('NO_ATLAS_INFO', 1)]
atlas_info:
NOT AVAILABLE
lapack_mkl_info:
NOT AVAILABLE
blas_mkl_info:
NOT AVAILABLE
atlas_blas_info:
NOT AVAILABLE
mkl_info:
NOT AVAILABLE
>>> numpy.test()
Running unit tests for numpy
NumPy version 1.7.1
NumPy is installed in /usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy
Python version 2.7.4 (default, Jul 22 2013, 15:21:29) [GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)]
nose version 1.3.0
.......S.........S..............................................................................................................
...............................................................................................................................
...............................................................................................................................

.... ....
.... ....

..........................................................................K....................................................
----------------------------------------------------------------------
Ran 4771 tests in 22.730s

OK (KNOWNFAIL=5, SKIP=7)
<nose.result.TextTestResult run=4771 errors=0 failures=0>
>>> quti()

OpenBLASを組み込んだ後のSciPyの動作テスト

% python
Python 2.7.4 (default, Jul 22 2013, 15:21:29)
[GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import scipy
>>> scipy.show_config()
blas_info:
libraries = ['openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
amd_info:
libraries = ['amd']
library_dirs = ['/usr/local/lib']
define_macros = [('SCIPY_AMD_H', None)]
swig_opts = ['-I/usr/local/include']
include_dirs = ['/usr/local/include']
lapack_info:
libraries = ['openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
atlas_threads_info:
NOT AVAILABLE
blas_opt_info:
libraries = ['openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
define_macros = [('NO_ATLAS_INFO', 1)]
atlas_blas_threads_info:
NOT AVAILABLE
umfpack_info:
libraries = ['umfpack', 'amd']
library_dirs = ['/usr/local/lib']
define_macros = [('SCIPY_UMFPACK_H', None), ('SCIPY_AMD_H', None)]
swig_opts = ['-I/usr/local/include', '-I/usr/local/include']
include_dirs = ['/usr/local/include']
lapack_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/usr/local/opt/openblas/lib']
language = f77
define_macros = [('NO_ATLAS_INFO', 1)]
atlas_info:
NOT AVAILABLE
lapack_mkl_info:
NOT AVAILABLE
blas_mkl_info:
NOT AVAILABLE
atlas_blas_info:
NOT AVAILABLE
mkl_info:
NOT AVAILABLE
>>> scipy.test()
Running unit tests for scipy
NumPy version 1.7.1
NumPy is installed in /usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy
SciPy version 0.12.0
SciPy is installed in /usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/scipy
Python version 2.7.4 (default, Jul 22 2013, 15:21:29) [GCC 4.2.1 Compatible Apple Clang 3.1 (tags/Apple/clang-318.0.61)]
nose version 1.3.0
/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy/lib/utils.py:139: DeprecationWarning: `scipy.lib.blas` is deprecated, use `scipy.linalg.blas` instead!
warnings.warn(depdoc, DeprecationWarning)
/usr/local/Cellar/python/2.7.4/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/numpy/lib/utils.py:139: DeprecationWarning: `scipy.lib.lapack` is deprecated, use `scipy.linalg.lapack` instead!
warnings.warn(depdoc, DeprecationWarning)
...............................................K...............................................K..K............................
...............................................................................................................................
...............................................................................................................................

.... ....
.... ....

...............................................................................................................................
----------------------------------------------------------------------
Ran 4829 tests in 59.444s

OK (KNOWNFAIL=11, SKIP=32)
<nose.result.TextTestResult run=4829 errors=0 failures=0>
>>> quit()

両方の結果が「OK」になっている。じつは、上のOpenBLASを組み込む前のSciPyの動作テスト結果が「FAILED」だったことが気になって、この問題の解決方法がないかと探しているうちに、OpenBLASの存在に行き着いた。なお、今回利用したsamueljohn/pythonリポジトリのNumPyとSciPyのフォーミュラ定義ファイルにはtestメソッドが定義されており、そのメソッドに上と同じnoseの「test()」関数を呼び出す処理が実装されている。numpyフォーミュラのインストール時にnoseライブラリを要求してくるのはこれが理由だ。フォーミュラをインストールした後で「brew test FORMULA」というコマンドをタイプすれば、指定したフォーミュラのtestメソッドを実行することができる。

また、上では「show_config()」という関数も使っているが、この関数を呼び出すと、NumPy/SciPyのコンフィグレーション情報を確認することができる。この情報を比較すると判るが、OpenBLASを組み込んだSciPyにはumfpackとamdという2つの外部ライブラリも一緒に追加されている。この2つのライブラリはSuiteSparseというフォーミュラの中に含まれているもので、numpyフォーミュラをインストールしたときにリンクされる。「show_config()」関数で表示されるコンフィグレーション情報の中に現れる用語の意味を下に示しておく。

 BLAS:Basic Linear Algebra Subprograms
  ベクトルと行列の基本演算を行うサブブログラム
 ATLAS:Automatically Tuned Linear Algebra Software
  コンパイル時に自動的にCPUを識別して、そのCPUに最適なBLASを構築するソフトウェア
 LAPACK:Linear Algebra Package
  連立一次方程式やベクトルと行列などの線形計算ルーチンを集めたパッケージ
 UMFPACK:Unsymmetric Multifrontal Sparse LU Factorization Package
  非対称疎線形系を非対称マルチフロンタル法を使って解くためのルーチンを集めたパッケージ
 AMD:Approximate Minimum Degree Ordering
  近似最小次数順序法(大規模行列並べ替えアルゴリズムの一種)
 MKL:Math Kernel Library
  特定のCPUに最適化された数値演算ライブラリ(Intel社のMKLやAMD社のACMLが有名)

OpenBLASを組み込んでもNumPy/SciPyの動作速度が大きく改善しなかったのは残念だったが、SciPyの動作テス結果が「FAILED」から「OK」に変わった瞬間は「よし。やったぜ」と叫んでしまった。

【参考ページ】

 Install Scientific Python on Mac OS X | Pen and Pants
 Errors in tests of Scipy ・ Issue #12 ・ samueljohn/homebrew-python ・ GitHub

posted by とみやん at 10:27| Comment(0) | TrackBack(0) | 開発・プログラミング > Mac
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/72191406

この記事へのトラックバック