Spark と MLlib
Sparkはオープンソースの分散処理フレームワークです。
Hadoopと比較した場合、インメモリで処理を行うため繰り返しの計算に強く、特に機械学習にメリットがあります。
Sparkは標準でMLlibという機械学習ライブラリを用意していて、__キラーアプリ__といえる存在になっています。
MLlibにはBLASのような著名なアルゴリズムが用意されています。
MLlib と Netlib
MLlibのアルゴリズムには、Javaで実装されたjblasが使用されています。
しかし、MLlibのパフォーマンスを高めるために、内部のアルゴリズムをネイティブの実装であるNetlibと差し替えたいと考えるのは自然なことでしょう。
Sparkはそのための手段を用意しています。
netlib-javaのビルド
SparkはJavaVM上で動作しますので、JNI経由でNetlibを呼び出すことになります。
JNIのラッパーはnetlib-javaと呼ばれています。
SparkでNetlibを使うためには、まず、netlib-javaをビルドする必要があります。
GitHubのREADMEにビルド手順が書かれていないので、非常に分かりにくい上に、pom.xmlに誤りがあったりとヘコみますが、ここを参考にしてビルドしましょう。
なお、blas-devel、lapack-devel、atlas-devel、libgfortranを事前にインストールしておきます。
$ 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が使えません。(ファイル名をリネームしてもよいですが)
$ 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のライセンスは__不明__が正しいようです。
今回はYARN対応と合わせてビルドします。
$ 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の設定については省略します。
# 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に配置しなくても自動でアップロードしてくれますが、毎回アップロードするのは無駄ですからね。
$ 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プロパティで強制的に指定することもできます。
- NativeSystemBLAS
- NativeRefBLAS
- F2jBLAS
NativeSystemBLAS と NativeRefBLAS の違いは不明です...。
詳しい方がいたら教えてください。
$ 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倍高速 という結果になりました。