Numpyを使うと、listなどを使うよりも処理が速くなります。
さらには、Numpy自体を高速化することも可能です。
本記事の内容
- Numpyの高速化
- 高速化Numpyのダウンロード
- 高速化Numpyのインストール
- ノーマルNumpyと高速化Numpyの比較
それでは、上記に沿って解説していきます。
Numpyの高速化
Numpyの高速化とは、コードの改善という話ではありません。
Numpyそのものをより高速なモノにするということです。
そのための技術として、MKLが存在します。
MKLは、Intelが開発している演算用のライブラリです。
もちろん、MKLはIntelのCPUのみに対応しています。
このMKLを利用すれば、Numpyを高速化できるというわけです。
そして、MKLに対応したNumpyが用意されています。
ここで、呼び名(表記)に関して整理しておきます。
説明しやすいように、以下の二つのNumpyの呼び名を用います。
- ノーマルNumpy(オリジナル・バニラともいう)
- 高速化Numpy(MKL対応)
本題に戻ると、Numpyの高速化とは、高速化Numpyを使うことだと言えます。
ただし、高速化Numpyのインストールはかなり大変です。
以下の3つを試しました。
- Numpyのビルド
- Intelチャンネルの利用
- ビルド済みパッケージの利用
それぞれを以下で説明します。
Numpyのビルド
MKLをインストールして、自分でNumpyをビルドする方法です。
正直、これはおススメできません。
かなりPC環境が汚れます。
MKLのダウンロードには、アカウント登録が必要です。
容量サイズも大きくて、ディスクが無駄に圧迫されます。
Intelチャンネルの利用
https://anaconda.org/intel/numpy
Intel公式だと安心できそうです。
しかし、現段階ではまだテストバージョンとのこと。
そして、pipで簡単にはインストールはできません。
依存関係を上手く管理できていないようです。
正式版になったら、再度試してみるのもいいかもしれません。
ビルド済みパッケージの利用
有志の方が、MKL+Numpyをビルドして公開してくれています。
詳細は、後述します。
結論で言うと、現在はビルド済みパッケージの利用が妥当です。
これなら、PC環境も汚れません。
また、ディスク容量も圧迫されません。
何よりも、簡単にインストールできます。
依存関係について何も考える必要がありません。
ただし、これはWindowsのみの話です。
macOSやLinuxの場合は、これ以外の方法で対応するしかありません。
まとめ
今回は、Windows上のNumpy高速化が対象です。
したがって、Numpyの高速化にはビルド済みパッケージを利用します。
あとは、Anacondaを利用するという方法もあります。
Anacondaなら、初めから高速化Numpyがインストールされているようです。
ライブラリにIDEが制限されるのは、個人的にはどうかなと思います。
なお、IDEはPyCharmを利用しています。
そもそも、Anacondaが嫌になってPyCharmを使うようになった経緯があります。
以上、Numpyの高速化についての説明でした。
次は、高速化Numpyのダウンロードを行います。
高速化Numpyのダウンロード
ダウンロードページ
https://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy
野良ページ(非公式)です。
カリフォルニア大学アーバイン校のChristoph Gohlke氏によるページとなります。
Gohlke氏は、多くのパッケージ(ビルド済み)を公開してくれています。
メンテナンスが行き届いていて、非常にありがたいです。
上記のページにアクセスすると、次のような表示を確認できます。
※Numpyはその時点の最新版が用意されている
ここから、各自の利用している環境にあったモノを選択します。
環境とは、具体的には以下となります。
- Pythonのバージョン
- アーキテクチャー(32bit or 64bit)
検証した環境は、以下の値となります。
Python | 3.9.6 | cp39 |
アーキテクチャー | 62bit | amd64 |
よって、上記の環境であれば、次のファイルを選択することになります。
numpy‑1.21.0+mkl‑cp39‑cp39‑win_amd64.whl
なお、現時点において、NumpyはPython 3.6をサポートしていません。
そのため、cp36用のファイルは存在していません。
では、該当するファイルをクリックしてダウンロードします。
ファイルは、200MB以上の容量があります。
ビルド済みのDLLを含むので、それぐらいの容量にはなるのでしょう。
以上、高速化Numpyのダウンロードを説明しました。
次は、高速化Numpyをインストールしていきます。
高速化Numpyのインストール
ダウンロードしたファイルを利用します。
まずは、現状のインストール済みパッケージを確認しておきます。
>pip list Package Version ---------- ------- pip 21.1.3 setuptools 57.1.0
次にするべきことは、pipとsetuptoolsの更新です。
pipコマンドを使う場合、常に以下のコマンドを実行しておきましょう。
python -m pip install --upgrade pip setuptools
では、高速化Numpyのインストールです。
高速化Numpyのインストールは、以下のコマンドとなります。
pip install ダウンロードしたファイル
今回の場合だと、以下。
pip install numpy‑1.21.0+mkl‑cp39‑cp39‑win_amd64.whl
インストールには、それほど時間がかかりません。
では、どんなパッケージがインストールされたのかを確認しましょう。
>pip list Package Version ---------- ---------- numpy 1.21.0+mkl pip 21.1.3 setuptools 57.1.0
numpyには、「+mkl」が追加されています。
これで、ノーマルのNumpyとは異なることがわかります。
あと、他のパッケージに依存していません。
このことにより、既存の環境にも導入しやすいと言えます。
さて、高速化Numpyの特徴はビルド不要でした。
それは、DLLをセットにしているからです。
ノーマルのNumpyには、そもそも「DLLs」というフォルダが存在していません。
それに対して、高速化Numpyには「DLLS」フォルダが存在しています。
その 「DLLs」には、以下のDLLが保存されています。
以上、高速化Numpyのインストールの説明でした。
最後に、高速化NumpyとノーマルNumpyの比較をします。
ノーマルNumpyと高速化Numpyの比較
Intel公式
https://software.intel.com/content/www/us/en/develop/articles/numpyscipy-with-intel-mkl.html
上記ページより、実行速度を計測するためのコードを拝借します。
利用マシンのCPUは、インテル Core i9 9900KFです。
import numpy as np import time N = 6000 M = 10000 k_list = [64, 80, 96, 104, 112, 120, 128, 144, 160, 176, 192, 200, 208, 224, 240, 256, 384] def get_gflops(M, N, K): return M * N * (2.0 * K - 1.0) / 1000 ** 3 np.show_config() for K in k_list: a = np.array(np.random.random((M, N)), dtype=np.double, order='C', copy=False) b = np.array(np.random.random((N, K)), dtype=np.double, order='C', copy=False) A = np.matrix(a, dtype=np.double, copy=False) B = np.matrix(b, dtype=np.double, copy=False) C = A * B start = time.time() C = A * B C = A * B C = A * B C = A * B C = A * B end = time.time() tm = (end - start) / 5.0 print('{0:4}, {1:9.7}, {2:9.7}'.format(K, tm, get_gflops(M, N, K) / tm))
上記コードの実行結果を確認します。
ノーマルNumpy
blas_mkl_info: NOT AVAILABLE blis_info: NOT AVAILABLE openblas_info: library_dirs = ['D:\\a\\1\\s\\numpy\\build\\openblas_info'] libraries = ['openblas_info'] language = f77 define_macros = [('HAVE_CBLAS', None)] blas_opt_info: library_dirs = ['D:\\a\\1\\s\\numpy\\build\\openblas_info'] libraries = ['openblas_info'] language = f77 define_macros = [('HAVE_CBLAS', None)] lapack_mkl_info: NOT AVAILABLE openblas_lapack_info: library_dirs = ['D:\\a\\1\\s\\numpy\\build\\openblas_lapack_info'] libraries = ['openblas_lapack_info'] language = f77 define_macros = [('HAVE_CBLAS', None)] lapack_opt_info: library_dirs = ['D:\\a\\1\\s\\numpy\\build\\openblas_lapack_info'] libraries = ['openblas_lapack_info'] language = f77 define_macros = [('HAVE_CBLAS', None)] 64, 0.08567424, 88.94156 80, 0.0995338, 95.84684 96, 0.1138956, 100.6185 104, 0.1161928, 106.8913 112, 0.136841, 97.77774 120, 0.1193839, 120.1167 128, 0.1126023, 135.8764 144, 0.1308503, 131.6008 160, 0.1321506, 144.8348 176, 0.1454113, 144.8306 192, 0.1550254, 148.2337 200, 0.1494, 160.241 208, 0.1654604, 150.4892 224, 0.1599717, 167.6546 240, 0.1852078, 155.1771 256, 0.174838, 175.3624 384, 0.2580142, 178.3622
高速化Numpy
blas_mkl_info: libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_rt'] library_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/lib/intel64_win'] define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)] include_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/include'] blas_opt_info: libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_rt'] library_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/lib/intel64_win'] define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)] include_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/include'] lapack_mkl_info: libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_rt'] library_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/lib/intel64_win'] define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)] include_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/include'] lapack_opt_info: libraries = ['mkl_lapack95_lp64', 'mkl_blas95_lp64', 'mkl_rt'] library_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/lib/intel64_win'] define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)] include_dirs = ['C:/Program Files (x86)/IntelSWTools/compilers_and_libraries_2020/windows/mkl/include'] 64, 0.02952104, 258.121 80, 0.03969369, 240.3405 96, 0.05465331, 209.6854 104, 0.05784545, 214.7101 112, 0.06303077, 212.2773 120, 0.05265942, 272.316 128, 0.05924153, 258.2648 144, 0.06642256, 259.2493 160, 0.07858987, 243.5428 176, 0.07360315, 286.129 192, 0.09325309, 246.4262 200, 0.08916178, 268.5007 208, 0.08158197, 305.2145 224, 0.09594383, 279.5386 240, 0.1022297, 281.1316 256, 0.1051193, 291.6685 384, 0.1595734, 288.3939
比較
確認するポイントは、2点。
- blas_mkl_infoの値
- 64, ●●●, ●●●以降の数値
blas_mkl_infoの値
ノーマルNumpyは、「NOT AVAILABLE」となっています。
MKLは、利用できない状況ということです。
それに対して、高速化Numpyでは値がズラズラと記載されています。
「mkl_rt」は、インストールしたDLLの中にありました。
blas_mkl_infoの値を見て、MKLの利用状況を確認できます。
64, ●●●, ●●●以降の数値
k_list = [64, 80, 96, 104, 112, 120, 128, 144, 160, 176, 192, 200, 208, 224, 240, 256, 384]
上記の値ごとに、表示されています。
数値が大きくなるほど、処理するデータが大きくなります。
その数値毎に、処理時間と処理性能が表示されています。
64, 0.02952104, 258.121 80, 0.03969369, 240.3405 96, 0.05465331, 209.6854 ・・・
まとめ
総じて、高速化Numpyの方が全体的に速いです。
その結果自体には、何ら驚きはありません。
ただ、興味深いのは、高速化NumpyのGFLOPS(処理性能)です。
初めからトップスピードで対応できるというところに、興味を持ちました。
そして、データのサイズによっては、2倍以上の速度が出ることが確認できました。
塵も積もればということで、大きな処理を行う場合には決して無視できない差になるでしょう。
ただ、これは推測に過ぎません。
別途、機械学習などの時間のかかる処理で検証してみたいと思います。