Spark ALS應用BLAS加速

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. 問題引入

  1. 在使用Spark1.6集羣,進行Spark ALS算法測試時,發現其推薦運行的很慢;
  2. 同時在推薦或建模時,出現如下的提示:
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. 參考:

此篇博客參考如下鏈接:

  1. Use Native BLAS/LAPACK in Apache Spark
  2. How to configure high performance BLAS/LAPACK for Breeze on Amazon EMR, EC2
  3. netlib-java#linux
  4. building spark
  5. 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

執行命令後,得到如下所示信息。

image

在圖中可以看到:

  1. 其使用的Classpath是Spark安裝目錄下的jars路徑下面的jar包;
  2. 代碼並沒有使用NativeSystemBLAS或NativeRefBLAS;
  3. 代碼測試耗時44.765秒。

4.2 使用Native BLAS需要添加的Jar包

方式1:在Intellij IDEA 中添加依賴找到

  1. 在IDEA工程中直接運行demo.TestBLAS object,即可看到當前使用的Classpath;
  2. 通過在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必須加入的參數。

如下所示,即爲編譯完成後結果。

image

4.3 使用新編譯的Spark測試是否加載Native BLAS

  1. 確認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.

可以看到有一個系統默認的庫。

  1. 拷貝編譯好的安裝包到node201,並在其下運行:
./spark-submit --class demo.TestBLAS \
--master local[1] \
/root/als_blas-1.0-SNAPSHOT.jar \
3000

運行後,可以看到如下信息:

image

從上面的信息可以看出:

  • 其加載了本地的BLAS庫
  • 雖然加載了本地的BLAS庫,但是還是很慢
  1. 嘗試安裝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:

出現如上所示信息,即說明安裝成功。

  1. 再次運行測試,如下:

image

說明:

  • 使用自行編譯的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從的配置,其集羣具體配置如下:
image

6.3 測試是否有BLAS的加速

  1. 直接運行測試類,命令如下:
spark-submit --class demo.AlsTest \
--deploy-mode cluster \
/root/als_blas-1.1-SNAPSHOT.jar 3000

經過多次測試,發現:

  • 耗時平均在1.2mins;

image

  • 子節點出現沒有使用Native BLAS的提示;

image

  1. 使用BLAS再次測試

(1)拷貝相關Jar包到所有集羣節點的$SPARK_HOME/jars;

(2)在集羣各個子節點安裝openblas,參考上面的命令;

(3)再次運行,發現其耗時仍是1.2mins,同時也仍有沒有使用Native BLAS的提示;

  1. 使用編譯的Spark 進行測試;
  • 耗時1.4mins,程序變得更慢;

image

  • 在子節點沒有出現未使用Native BLAS的提示,說明已經使用了BLAS庫;

7. 總結

  1. 如果要使用Spark ALS算法,建議使用Spark2.x以上,效率更快;
  2. 如果只能使用Spark1.x,建議使用自行編譯的Spark安裝包,可以應用Native BLAS進行加速([有待驗證!])
  3. 如果使用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))
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章