MKL版のnumpyを使ってみたら爆速だった（Core i9 9900K, Core i9 10980XE, Ryzen 9 3900X）

AMD Ryzenでそのままnumpyを動かすと遅くなるので、mklを利用できるnumpyをインストール必要があることを知りました。

どの程度変わるのか手元の環境で確認したら、Intel版でも爆速になったので、インストール手順と合わせてパフォーマンス比較結果をまとめたいと思います。

# 環境
## Xeon E5-1650 v4
CPU : Xeon E5-1650 v4 6C/12T @3.6GHz
RAM : DDR4-2133 8GB x 8 (Total 64GB)
OS : Ubuntu 20.04 (Docker)

## Core i9 9900K
CPU : Core i9 9900K 8C/16T @3.6GHz
RAM : DDR4-2133 16GB x 4 (Total 64GB)
OS : Ubuntu 20.04 (Docker)

## Core i9 10980XE
CPU : Core i9 10980XE 18C/36T @3.0GHz
RAM : DDR4-2133 32GB x 8 (Total 256GB)
OS : Ubuntu 20.04 (Docker)

## Ryzen 9 3900X
CPU : Ryzen 9 3900X 12C/24T @3.8GHz
RAM : DDR4-3600 32GB x 2 (Total 64GB)
OS : Ubuntu 20.04 (Docker)


# mkl版numpy（&scipy) のインストール方法
## ローカル環境
まず、Intel MKLをインストールします。

```
wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB \
    && sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB \
    && rm GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB
```

```
sudo wget https://apt.repos.intel.com/setup/intelproducts.list -O /etc/apt/sources.list.d/intelproducts.list
```
```
sudo apt update && sudo apt install -y intel-mkl-2020.0-088
```

※インストールするバージョンは適宜変更してください。（https://software.intel.com/content/www/us/en/develop/tools/oneapi/base-toolkit/download.html?operatingsystem=linux）

pipでインストールされているnumpyとscipyをアンインストールします。

```
python3 -m pip uninstall -y numpy scipy
```

`~/.numpy-site.cfg` にmkl driverを利用するよう記述します。

```
echo "[mkl] \n\
    library_dirs = /opt/intel/mkl/lib/intel64 \n\
    include_dirs = /opt/intel/mkl/include \n\
    mkl_libs = mkl_rt \n\
    lapack_libs =" > ~/.numpy-site.cfg
```

強制的にビルドするオプションを付けて、numpyとscipyをインストールします。

```
python3 -m pip install --no-binary :all: numpy
```

```
python3 -m pip install --no-binary :all: scipy
```


## Docker環境
Dockerfileに下記のように記述します。

```
# install mkl
WORKDIR /tmp
RUN wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB \
    && apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB \
    && rm GPG-PUB-KEY-INTEL-SW-PRODUCTS-2019.PUB
RUN wget https://apt.repos.intel.com/setup/intelproducts.list -O /etc/apt/sources.list.d/intelproducts.list
RUN apt update && apt install -y \
    intel-mkl-2020.0-088

# install numpy of mkl version
RUN python3 -m pip uninstall -y numpy scipy
RUN echo "[mkl] \n\
    library_dirs = /opt/intel/mkl/lib/intel64 \n\
    include_dirs = /opt/intel/mkl/include \n\
    mkl_libs = mkl_rt \n\
    lapack_libs =" > ~/.numpy-site.cfg
RUN python3 -m pip install --no-binary :all: numpy
RUN python3 -m pip install --no-binary :all: scipy
```


# パフォーマンス評価
## テスト関数
下記のテスト関数でパフォーマンスを計測しました。

```
import numpy as np
import time

N_LOOP = 50

def testfunc(x):
    np.random.seed(x)
    X = np.random.randn(2000, 4000)
    np.linalg.eigh(X @ X.T)


calc_time_list = []
for i in range(N_LOOP):
    t_start = time.time()
    testfunc(0)
    t_end = time.time()
    calc_time_list.append(t_end - t_start)
    print("[{} / {}] calculation time : {:.4f} s".format(i, N_LOOP, t_end - t_start))

calc_time_ndarr = np.array(calc_time_list)
print("===========================================================")
np.show_config()
print("===========================================================")
print("ave : {:.4f} s, std : {:.4f} s, min : {:.4f} s, max : {:.4f} s".format(np.average(calc_time_ndarr),
                                                                              np.std(calc_time_ndarr),
                                                                              np.min(calc_time_ndarr),
                                                                              np.max(calc_time_ndarr)))
```

## テスト条件
環境に示した4種類について、OpenBLASとMKLでそれぞれ測定しました。`MKL_DEBUG_CPU_TYPE` による効果を確認するため、Ryzen 9 3900Xのみ、`MKL_DEBUG_CPU_TYPE=5` として測定も行いました。
各測定50回ずつ行い、その平均値を比較しています。

## テスト結果
### `MKL_NUM_THREADS` を特に指定せず測定した場合
計算時間（低いほうが良い）
![chart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477282/ff3e17ad-892a-7386-1cbf-a48d5561680e.png)

Xeon E5-1650v4を基準とした相対性能（高いほうが良い）
![chart (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477282/13e6d32b-2f67-3631-7d7a-61a3919b4066.png)


MKLを使用した場合、Intel・AMDに依らず、OpenBLASに比べて大幅に性能が向上することを確認できました。およそ40倍程度高速になっています。
また、AMD環境でMKLを使用している場合、`MKL_DEBUG_CPU_TYPE=5` とすることで、若干の性能向上が期待できることを確認できました。
今回の検証では、`MKL_DEBUG_CPU_TYPE=5` とすることで、Ryzen 9 3900XがCore i9 9900Kを逆転し、最も性能が高くなっています。

一方で、最もスレッド数の多いCore i9 10980XEの性能が思ったよりも振るわなかったのは、シングルスレッド性能の低さの影響でしょうか。
それを検証するため、今度は `MKL_NUM_THREADS=1` として、純粋なシングルスレッド性能を見てみました。

### `MKL_NUM_THREADS=1`としてシングルスレッドで測定した場合（一部環境、MKLのみ）
計算時間（低いほうが良い）
![chart (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477282/d297cf88-5ca1-7802-4462-02ff210ee0e9.png)

Ryzen 9 3900Xを基準とした相対性能（高いほうが良い）
![chart (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477282/182df9a8-14b0-c270-15b9-1255f55755e3.png)

今度はCore i9 9900Kが最も性能良いという結果になりました。CINEBENCH R20のシングルスレッド性能で比較すると、Core i9 9900Kが518、Ryzen 9 3900Xが502、Core i9 10980XEが480なので、ほぼ予想通りの結果と言えると思います。
以上の結果から、MKL版のnumpyで`MKL_DEBUG_CPU_TYPE=5`とすることにより、AMD Ryzenでも性能を出せることが確認できました。

# まとめ

MKL版のnumpyがOpenBLAS版よりも想像以上に早かったため、比較記事にしてみました。個人的にわかったこととしては下記2点です。

- Intel、AMDに依らず、numpyを使うなら手動ビルドでMKL版のnumpyをインストールしたほうが良い
- AMD Ryzenの場合、`MKL_DEBUG_CPU_TYPE=5`とすると若干性能が向上する

以上、参考になれば幸いです。

# 参考
- https://engineer-milione.com/programming/ryzen-numpyl.html
- https://tech.morikatron.ai/entry/2020/03/27/100000
- https://qiita.com/hotchpotch/items/a17356433ddfbbc991a0
- https://software.intel.com/content/www/us/en/develop/tools/oneapi/base-toolkit/download.html?operatingsystem=linux