この記事は,高速な数値計算ライブラリIntel Math Kernel Library(Intel MKL)を,Windows Subsystem for Linux(WSL)上でJavaから呼び出すまでのまとめ記事です.WSLがインストールされている前提で話を進めます.

以下の順にまとめていきます.

  1. Intel MKLのインストール
  2. Intel MKLのC++からの利用
  3. Intel MKLの共有ライブラリへのラッピング
  4. 共有ライブラリのJavaからの利用
  5. まとめ

Intel MKLのインストール

本節では,Intel MKLをWSLのUbuntu上のユーザの$HOME/programs/intelにインストールする手順について説明します.

インストーラのダウンロード

  1. このページにアクセスし,”Free Download”をクリック.
  2. 以下のページに移動する.適宜登録を済ませ,ログインした状態でユーザ情報を”Submit”する.
  3. 以下のページに移動する.”Intel Products”をクリック.
  4. 以下のページに移動する.Intel Performance Library for Linuxの隣にある”Version 2018”をクリック.
  5. Choose a Versionから”2018 Update 1”を選択し,Choose a Download Option内の”Intel Math Kernel Libray(Intel MKL)”をクリックし,インストーラ(l_mkl_2018.1.163.tar.gz)をダウンロード

インストール

  1. l_mkl_2018.1.163.tar.gzを$HOME/programs/に移動
  2. tar xvf l_mkl_2018.1.163.tar.gz で解凍
  3. $HOME/programs/l_mkl_2018.1.163/install.sh を実行
  4. インストール対象をユーザに設定し,インストールディレクトリに$HOME/programs/intelを指定

Intel MKLのC++からの利用

本節では,前節でインストールしたIntel MKLをWSL上でC++プログラムから呼び出す手順について説明します. ライブラリパス,インクルードパス,LD_LIBRARY_PATHを適切に指定することでIntel MKLを呼び出すことができます.

行列x行列の演算を行う関数である,cblas_dgemmを呼び出すプログラム (test.cpp) を用いて説明します.

1.以下のようにtest.cppを作成.

#include <iostream>
#include "mkl.h"

void A_TB(const int m, const int n, const int k,
                     const double A[], const double B[], double C[])
{
    double alpha = 1.0, beta = 0.0;
    cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k,
          alpha, A, k,
          B, n, beta,
          C, m);
}

int main(){
        std::cout << "Intel MKL test" << std::endl;
        const double A[] = {1, 2,
                            3, 4};
        const double B[] = {1, 3,
                            4, 2};
        double C[] = {0, 0, 0, 0};
        const int m = 2, n = 2, k = 2;

        std::cout << "A" << std::endl;
        std::cout << A[0] << ", " << A[1] << std::endl;
        std::cout << A[2] << ", " << A[3] << std::endl;

        std::cout << "B" << std::endl;
        std::cout << B[0] << ", " << B[1] << std::endl;
        std::cout << B[2] << ", " << B[3] << std::endl;

        A_TB(m, n, k, A, B, C);

        std::cout << "calculate AB" << std::endl;
        std::cout << C[0] << ", " << C[1] << std::endl;
        std::cout << C[2] << ", " << C[3] << std::endl;

        return 0;
}

2.コンパイルに必要なライブラリをファイルに書き込んでおく.ここでは,liblistというファイルに以下の内容を書き込む.

-lmkl_avx2
-lmkl_avx512_mic
-lmkl_avx512
-lmkl_avx
-lmkl_core
-lmkl_def
-lmkl_mc3
-lmkl_mc
-lmkl_vml_avx2
-lmkl_vml_avx512_mic
-lmkl_vml_avx512
-lmkl_vml_avx
-lmkl_vml_cmpt
-lmkl_vml_def
-lmkl_vml_mc2
-lmkl_vml_mc3
-lmkl_vml_mc
-lmkl_rt

3.以下のように,ライブラリパスとインクルードパスを設定し,g++でビルドを行う.

g++ -std=c++11 -L$HOME/programs/intel/mkl/lib/intel64 -I$HOME/programs/intel/mkl/include test.cpp `cat liblist` -o test.out

4.LD_LIBRARY_PATHの設定.

export LD_LIBRARY_PATH=$HOME/programs/intel/mkl/lib/intel64

5.test.outの実行.

./test.out

以下の出力が得られれば,無事にIntel MKLをC++から呼び出せているはずです.

Intel MKL test
A
1, 2
3, 4
B
1, 3
4, 2
calculate AB
9, 7
19, 17

Intel MKLの共有ライブラリへのラッピング

本節では,Intel MKLを共有ライブラリにラッピングする手順について説明します.共有ライブラリにラッピングすることで,C++以外からもIntel MKLを呼び出すことが可能になります.

1.以下のように,共有ライブラリを作成するためのC++ファイルとヘッダーファイルを作成する. このように,Intel MKL内の使用したい関数を用いた関数を新たに定義する.

mkl_blas_for_java.cpp

#include "mkl.h"
#include "mkl_blas_for_java.hpp"

/* MKL CBlas Level 3 */
extern "C" void aM1_TM2_PbM0(const int M1M0_row, const int M2M0_col, const int M1_colM2_row,
                             const double a, const double M1[], const double M2[], const double b, double M0[])
{
    const CBLAS_LAYOUT Layout = CblasRowMajor;
    const CBLAS_TRANSPOSE trans = CblasNoTrans;
    cblas_dgemm(Layout, trans, trans, M1M0_row, M2M0_col, M1_colM2_row,
          a, M1, M1_colM2_row, M2, M2M0_col, b, M0, M2M0_col);
}

mkl_blas_for_java.hpp

#ifndef MKL_BLAS_FOR_JAVA_HPP
#define MKL_BLAS_FOR_JAVA_HPP

/* MKL Blas Level 3 */
extern "C" void aM1_TM2_PbM0(const int M1M0_row, const int M2M0_col, const int M1_colM2_row,
                             const double a, const double M1[], const double M2[], const double b, double M0[]);

#endif

2.以下のようにして,共有ライブラリを作成する.

g++ -fPIC -std=c++11 -Ofast -L$HOME/programs/intel/mkl/lib/intel64 -I$HOME/programs/intel/mkl/include -shared -o libmkljava.so mkl_blas_for_java.cpp `cat liblist`

libmkljava.soが作成されれば成功です.

共有ライブラリのJavaからの利用

本節では,前節で作成した共有ライブラリ(libmkljava.so)をJavaから呼び出す手順について説明します. 共有ライブラリをJavaから呼び出すことで,共有ライブラリ内で使用しているIntel MKL関数をJavaで利用できるようになります. 共有ライブラリの呼び出しには,jna-4.1.0のライブラリを使用します.

1.以下のように,Javaインターフェースファイルを作成.

mklFunction.java

import com.sun.jna.Library;
import com.sun.jna.Native;

public interface mklFunction extends Library {
    mklFunction INSTANCE = (mklFunction) Native.loadLibrary("mkljava", mklFunction.class);

    /* MKL Blas Level 3 */
    void aM1_TM2_PbM0(final int M1M0_row, final int M2M0_col, final int M1_colM2_row,
                      final double a, final double M1[], final double M2[], final double b, double M0[]);
}

2.インターフェース内の関数を呼び出すJavaファイルを作成.

mklTest.java

public class mklTest2 {
    public static void main(String[] args) {
        final mklFunction2 func = mklFunction2.INSTANCE;

        double M1[] = new double[]{1, 2,
                                   3, 4};
        double M2[] = new double[]{1, 3,
                                   4, 2};
        double M0[] = new double[]{0, 0, 0, 0};
        int m = 2, n = 2, k = 2;
        double a = 1.0, b = 0.0;
        System.out.println("Intel MKL test");
        func.aM1_TM2_PbM0(m, n, k, a, M1, M2, b, M0);
        System.out.println(M0[0] + ", " + M0[1]);
        System.out.println(M0[2] + ", " + M0[3]);
    }
}

3.以下のようにして,Javaクラスファイルを作成.

javac -cp ./jna-4.1.0.jar mklTest.java mklFunction.java

4.以下のようにして,Javaプログラムを実行.java -cp 以下には,libmkljava.soのあるフォルダを指定.ここでは,現在のフォルダを指定している.

export LD_LIBRARY_PATH=$HOME/programs/intel/mkl/lib/intel64
java -cp .:./jna-4.1.0.jar mklTest

以下の出力が得られれば,無事にIntel MKLをJavaから呼び出せているはずです.

Intel MKL test
9.0, 7.0
19.0, 17.0

まとめ

以上で,WSL上でJavaからIntel MKLを呼び出す手順についての説明は終わりです. これで,Javaからでもなかなか高速な行列計算ができるようになります.うれしいですね.

私の体感では,の計算(行列x行列とか逆行列の計算)は圧倒的に早くなる気がします.それ以外のベクトルx行列とかはあんまり早くなってる気がしませんでした.元々Javaの段階で結構早いからなのでしょうか?いまいちわかりません.わかる人がいたら教えてください.

まあ,この関数を利用して,1000x1000ぐらいのサイズの行列の積をたくさん計算させると,CPU使用率が100%になって気持ちがいいです.

もちろん,上記で示した以外の関数も,同様の手順でJavaから呼び出すことが可能です.

参考までに,私が実装した分はGitHubに置いておきます.