文章目錄
Spark ALS應用BLAS加速
1. 環境
軟件 | 說明 | 版本 |
---|---|---|
Win10 | 宿主機 | 8G內存,179G SSD |
VMware Workstation | 虛擬化軟件 | V11 |
Spark | 2.2.2 | |
Hadoop | 2.6.5 | |
Maven | windows 上maven用於編譯Spark2.2.2 | 3.3.9 |
Intellij IDEA | Windows上編譯測試包 | 2016.3 |
ubuntu | WMware 虛擬機系統 | Ubuntu 16.04.5 LTS |
集羣 | 一主三從,node200(主), node201~node203 |
2. 問題引入
- 在使用Spark1.6集羣,進行Spark ALS算法測試時,發現其推薦運行的很慢;
- 同時在推薦或建模時,出現如下的提示:
Feb 25, 2019 10:07:05 PM com.github.fommil.netlib.BLAS <clinit>
WARNING: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS
Feb 25, 2019 10:07:05 PM com.github.fommil.netlib.BLAS <clinit>
WARNING: Failed to load implementation from: com.github.fommil.netlib.NativeRefBLAS
com.github.fommil.netlib.F2jBLAS
3. 參考:
此篇博客參考如下鏈接:
- Use Native BLAS/LAPACK in Apache Spark
- How to configure high performance BLAS/LAPACK for Breeze on Amazon EMR, EC2
- netlib-java#linux
- building spark
- spark mllib guide
4. 思路:
4.1 簡單測試:
參考 2 中的代碼,並添加可以打印當前使用的Classpath路徑功能的代碼,全部代碼可以 fansy1990/als_blas 中進行checkout。
下載完代碼後,直接導入到IntelliJ IDEA中,並編譯打包,得到als_blas-1.0-SNAPSHOT.jar,然後即可執行:
./spark-submit --class demo.TestBLAS --master local[1] /root/als_blas-1.0-SNAPSHOT.jar 3000
執行命令後,得到如下所示信息。
在圖中可以看到:
- 其使用的Classpath是Spark安裝目錄下的jars路徑下面的jar包;
- 代碼並沒有使用NativeSystemBLAS或NativeRefBLAS;
- 代碼測試耗時44.765秒。
4.2 使用Native BLAS需要添加的Jar包
方式1:在Intellij IDEA 中添加依賴找到
- 在IDEA工程中直接運行demo.TestBLAS object,即可看到當前使用的Classpath;
- 通過在IDEA工程的pom.xml文件中加入
<dependency>
<groupId>com.github.fommil.netlib</groupId>
<artifactId>all</artifactId>
<version>1.1.2</version>
<type>pom</type>
</dependency>
依賴後,重新運行,可以發現其加載多了幾個Jar包,如下:
jniloader-1.1.jar
netlib-native_ref-win-i686-1.1-natives.jar
netlib-native_system-win-i686-1.1-natives.jar
native_system-java-1.1.jar
native_ref-java-1.1.jar
netlib-native_ref-linux-armhf-1.1-natives.jar
netlib-native_system-linux-armhf-1.1-natives.jar
netlib-native_ref-linux-i686-1.1-natives.jar
netlib-native_system-linux-i686-1.1-natives.jar
netlib-native_ref-osx-x86_64-1.1-natives.jar
netlib-native_system-osx-x86_64-1.1-natives.jar
netlib-native_ref-win-x86_64-1.1-natives.jar
netlib-native_system-win-x86_64-1.1-natives.jar
netlib-native_ref-linux-x86_64-1.1-natives.jar
netlib-native_system-linux-x86_64-1.1-natives.jar
此Jar包即是所需的Jar包。
方式2: 自行指定參數編譯Spark源碼
下載Spark源碼,並編譯,編譯時使用如下命令及參數:
C:\"Program Files"\apache-maven-3.6.0-bin\apache-maven-3.3.9\bin\mvn \
-Pnetlib-lgpl \
-Pyarn \
-Phive\
-Phive-thriftserver \
-DskipTests \
clean package
其中,-netlib-lgpl就是使用Native BLAS必須加入的參數。
如下所示,即爲編譯完成後結果。
4.3 使用新編譯的Spark測試是否加載Native BLAS
- 確認node201上是否有blas庫,如下:
root@node201:~# update-alternatives --config libblas.so
update-alternatives: error: no alternatives for libblas.so
root@node201:~# update-alternatives --config libblas.so.3
There is only one alternative in link group libblas.so.3 (providing /usr/lib/libblas.so.3): /usr/lib/libblas/libblas.so.3
Nothing to configure.
可以看到有一個系統默認的庫。
- 拷貝編譯好的安裝包到node201,並在其下運行:
./spark-submit --class demo.TestBLAS \
--master local[1] \
/root/als_blas-1.0-SNAPSHOT.jar \
3000
運行後,可以看到如下信息:
從上面的信息可以看出:
- 其加載了本地的BLAS庫
- 雖然加載了本地的BLAS庫,但是還是很慢
- 嘗試安裝openblas
apt-get install libatlas3-base libopenblas-base
安裝完成後,進行驗證,如下:
root@node201:/opt/spark-2.2.2/bin# update-alternatives --config libblas.so
update-alternatives: error: no alternatives for libblas.so
root@node201:/opt/spark-2.2.2/bin# update-alternatives --config libblas.so.3
There are 3 choices for the alternative libblas.so.3 (providing /usr/lib/libblas.so.3).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/lib/openblas-base/libblas.so.3 40 auto mode
1 /usr/lib/atlas-base/atlas/libblas.so.3 35 manual mode
2 /usr/lib/libblas/libblas.so.3 10 manual mode
3 /usr/lib/openblas-base/libblas.so.3 40 manual mode
Press <enter> to keep the current choice[*], or type selection number:
出現如上所示信息,即說明安裝成功。
- 再次運行測試,如下:
說明:
- 使用自行編譯的Spark能加載安裝的openblas;
- 同時效率有了10x的提升!
5. 修改官網提供的安裝包,使其加載BLAS
5.1 使用 --jars 參數
既然對比發現官網和自己編譯打包的Spark安裝包裏面的Classpath只有幾個不同的jar包,可以考慮把這些jar包加入到執行參數中,如下:
./spark-submit --class demo.TestBLAS \
--master local[1] \
--jars /root/jars/jniloader-1.1.jar,/root/jars/netlib-native_ref-win-x86_64-1.1-natives.jar,/root/jars/native_ref-java-1.1.jar,/root/jars/netlib-native_system-linux-armhf-1.1-natives.jar,/root/jars/native_system-java-1.1.jar,/root/jars/netlib-native_system-linux-i686-1.1-natives.jar,/root/jars/netlib-native_ref-linux-armhf-1.1-natives.jar,/root/jars/netlib-native_system-linux-x86_64-1.1-natives.jar,/root/jars/netlib-native_ref-linux-i686-1.1-natives.jar,/root/jars/netlib-native_system-osx-x86_64-1.1-natives.jar,/root/jars/netlib-native_ref-linux-x86_64-1.1-natives.jar,/root/jars/netlib-native_system-win-i686-1.1-natives.jar,/root/jars/netlib-native_ref-osx-x86_64-1.1-natives.jar,/root/jars/netlib-native_system-win-x86_64-1.1-natives.jar,/root/jars/netlib-native_ref-win-i686-1.1-natives.jar \
/root/als_blas-1.0-SNAPSHOT.jar \
3000
但是,經測試此方法失敗,[此方法的失敗在 1 中有提及]。
5.2 直接拷貝相關Jar包到$SPARK_HOME/jars路徑中;
拷貝相關包到官網安裝包的jars路徑下:
cp /root/jars/* $SPARK_HOME/jars
測試通過,說明:拷貝相關包到$SPARK_HOME/jars的方式可行 !
6. 測試ALS是否有加速
本文問題的引入是因爲使用Spark1.6版本,但是測試環境使用的是Spark2.2.2版本,故此測試環境可能也會有影響,例如Spark2.2.2是有對ALS做了優化的,下面也有提及此點。
6.1 測試數據
測試數據使用 MovieLens 1m dataset, 該數據集有6000用戶,4000電影。
6.2 使用集羣配置
集羣使用本機虛擬機,1主3從的配置,其集羣具體配置如下:
6.3 測試是否有BLAS的加速
- 直接運行測試類,命令如下:
spark-submit --class demo.AlsTest \
--deploy-mode cluster \
/root/als_blas-1.1-SNAPSHOT.jar 3000
經過多次測試,發現:
- 耗時平均在1.2mins;
- 子節點出現沒有使用Native BLAS的提示;
- 使用BLAS再次測試
(1)拷貝相關Jar包到所有集羣節點的$SPARK_HOME/jars;
(2)在集羣各個子節點安裝openblas,參考上面的命令;
(3)再次運行,發現其耗時仍是1.2mins,同時也仍有沒有使用Native BLAS的提示;
- 使用編譯的Spark 進行測試;
- 耗時1.4mins,程序變得更慢;
- 在子節點沒有出現未使用Native BLAS的提示,說明已經使用了BLAS庫;
7. 總結
- 如果要使用Spark ALS算法,建議使用Spark2.x以上,效率更快;
- 如果只能使用Spark1.x,建議使用自行編譯的Spark安裝包,可以應用Native BLAS進行加速([有待驗證!])
- 如果使用Spark2.x的自行編譯的安裝包,那麼針對Spark中ALS算法,其推薦效率更低。這點其實在其源碼中也有說明,如下所示。
private def recommendForAll(
rank: Int,
srcFeatures: RDD[(Int, Array[Double])],
dstFeatures: RDD[(Int, Array[Double])],
num: Int): RDD[(Int, Array[(Int, Double)])] = {
val srcBlocks = blockify(srcFeatures)
val dstBlocks = blockify(dstFeatures)
val ratings = srcBlocks.cartesian(dstBlocks).flatMap { case (srcIter, dstIter) =>
val m = srcIter.size
val n = math.min(dstIter.size, num)
val output = new Array[(Int, (Int, Double))](m * n)
var i = 0
val pq = new BoundedPriorityQueue[(Int, Double)](n)(Ordering.by(_._2))
srcIter.foreach { case (srcId, srcFactor) =>
dstIter.foreach { case (dstId, dstFactor) =>
// We use F2jBLAS which is faster than a call to native BLAS for vector dot product
val score = BLAS.f2jBLAS.ddot(rank, srcFactor, 1, dstFactor, 1)
pq += dstId -> score
}
pq.foreach { case (dstId, score) =>
output(i) = (srcId, (dstId, score))
i += 1
}
pq.clear()
}
output.toSeq
}
ratings.topByKey(num)(Ordering.by(_._2))
}