Spark MLlib でネイティブライブラリを使う

# Spark と MLlib

[Spark](http://spark.apache.org/)はオープンソースの分散処理フレームワークです。
[Hadoop](http://hadoop.apache.org/)と比較した場合、インメモリで処理を行うため繰り返しの計算に強く、特に機械学習にメリットがあります。
Sparkは標準で[MLlib](https://spark.apache.org/mllib/)という機械学習ライブラリを用意していて、__キラーアプリ__といえる存在になっています。
MLlibには[BLAS](http://www.netlib.org/blas/)のような著名なアルゴリズムが用意されています。

# MLlib と Netlib

MLlibのアルゴリズムには、Javaで実装された[jblas](https://github.com/mikiobraun/jblas)が使用されています。
しかし、MLlibのパフォーマンスを高めるために、内部のアルゴリズムをネイティブの実装である[Netlib](http://www.netlib.org/)と差し替えたいと考えるのは自然なことでしょう。
Sparkはそのための手段を用意しています。

# netlib-javaのビルド

SparkはJavaVM上で動作しますので、JNI経由でNetlibを呼び出すことになります。
JNIのラッパーは[netlib-java](https://github.com/fommil/netlib-java)と呼ばれています。
SparkでNetlibを使うためには、まず、netlib-javaをビルドする必要があります。
GitHubのREADMEにビルド手順が書かれていないので、非常に分かりにくい上に、pom.xmlに誤りがあったりとヘコみますが、[ここ](https://github.com/PasaLab/marlin/issues/1)を参考にしてビルドしましょう。
なお、blas-devel、lapack-devel、atlas-devel、libgfortranを事前にインストールしておきます。

``` console
$ git clone https://github.com/fommil/netlib-java.git
$ cd netlib-java
$ git checkout -b 1.1.2 refs/tags/1.1.2
$ sed -i "s/1.2-SNAPSHOT/1.1.2/g" `grep -l 1.2-SNAPSHOT pom.xml perf/pom.xml legacy/pom.xml`          # pom.xmlを修正する
$ sed -i "s/1.1.1/1.1.2/g" `grep -l 1.1.1 generator/pom.xml core/pom.xml all/pom.xml`                 # pom.xmlを修正する
$ sed -i "s/1.2-SNAPSHOT/1.1/g" `grep -rl --include='pom.xml' 1.2-SNAPSHOT native_ref native_system`  # pom.xmlを修正する
$ mvn package                                               # エラーは無視する
$ cd native_system
$ mvn package                                               # エラーは無視する
$ cd xbuilds/linux-x86_64
$ mvn package
$ cd ../../../native_ref
$ mvn package                                               # エラーは無視する
$ cd xbuilds/linux-x86_64
$ mvn package
```

ビルドしてできたsoファイルをインストールします。
残念ながら、ファイル名が _lib*.so_ 形式になっていないので、ldconfigが使えません。（ファイル名をリネームしてもよいですが）

``` console
$ cd netlib-java
# cp native_system/xbuilds/linux-x86_64/target/netlib-native_system-linux-x86_64.so /usr/lib64
# cp native_ref/xbuilds/linux-x86_64/target/netlib-native_ref-linux-x86_64.so /usr/lib64
```

# Netlibを有効にしてSparkをビルドする

netlib-javaをインストールしただけではSparkから利用できません。
Netlibを使うようにSparkをビルドする必要があります。
-Pオプションにnetlib-lgplを追加するだけなので簡単です。
LGPLとなっていますが、[Netlibのライセンス](http://www.netlib.org/lapack/LICENSE)は__不明__が正しいようです。
今回はYARN対応と合わせてビルドします。

``` console
$ git clone https://github.com/apache/spark.git
$ cd spark
$ git checkout -b v1.3.1 refs/tags/v1.3.1
$ ./make-distribution.sh --tgz -Phadoop-2.4 -Pyarn -Pnetlib-lgpl -DskipTests
```

ビルドしたSparkをインストールします。
お好みの方法でどうぞ。
Sparkの設定については省略します。

``` console
# cp spark-1.3.1-bin-2.4.0.tgz /usr/local/
# cd /usr/local
# tar xvf spark-1.3.1-bin-2.4.0.tgz
# chown -R spark:hadoop spark-1.3.1-bin-2.4.0
# ln -s spark-1.3.1-bin-2.4.0 spark
```

ビルドしたspark-assemblyをHDFSに配置します。
HDFSに配置しなくても自動でアップロードしてくれますが、毎回アップロードするのは無駄ですからね。

``` console
$ cd spark
$ hdfs dfs -put lib/spark-assembly-1.3.1-hadoop2.4.0.jar /user/spark/share/lib/spark-assembly.jar
$ hdfs dfs -chown spark /user/spark/share/lib/spark-assembly.jar
```

# 動作確認

SparkShell を起動して確認します。
ネイティブライブラリを使用するか否かは以下の順番で試行されます。
また、com.github.fommil.netlib.BLASプロパティで強制的に指定することもできます。

1. NativeSystemBLAS
1. NativeRefBLAS
1. F2jBLAS

NativeSystemBLAS と NativeRefBLAS の違いは不明です...。
詳しい方がいたら教えてください。

``` console
$ spark-shell --master yarn
$ spark-shell --master yarn --driver-java-options -Dcom.github.fommil.netlib.BLAS=com.github.fommil.netlib.F2jBLAS    # F2jBLASを強制的に使用する場合
：
Spark context available as sc.
scala> import com.github.fommil.netlib.BLAS
import com.github.fommil.netlib.BLAS
：
scala> println(BLAS.getInstance().getClass().getName())
15/05/01 14:22:12 INFO JniLoader: successfully loaded /usr/lib64/netlib-native_system-linux-x86_64.so
com.github.fommil.netlib.NativeSystemBLAS
```

__NativeSystemBLAS__ が表示されれば成功です。

# ベンチマーク

簡単ですが、ベンチマークとしてDenseMatrix(1000,1000)の掛け算を実行してみました。
疑似分散環境なので参考程度ですが。

|     |NativeSystemBLAS|NativeRefBLAS|F2jBLAS|
|-----|-----|-----|-----|
|1回目|399|830|1105|
|2回目|362|834|1091|
|3回目|337|814|1452|
（単位：ms）

F2jBLAS に比べて NativeSystemBLAS は __3.3倍高速__ という結果になりました。