[Spark] 代码性能优化笔记

本文记录了作者通过查看spark UI中Executors记录,成功定位到性能较差代码的位置。进而进行代码优化后,将任务执行时间从4hour优化到30min。

背景

今天用spark执行了数据量并不算大的任务,但是点进Stages发现,任务执行很慢。猜测代码性能较差,请教了周围的大神,总结出如下定位性能较差代码位置的方法。

定位方法

第一步

进入Executors,找到Active Tasks为1的Executor ID,一般为蓝色,如图一。

图一

第二步

找到Active Tasks为1的Executor ID后,点击Thread Dump进入,寻找Thread State 为 RUNNABLE 的 thread id,如图二、图三。
图二
图三

第三步

找到为 RUNNABLE 的 thread id 后,点击展开,仔细查看调用栈中自己所写程序的部分,如图四。发现问题出在83行,以及40行,找到对应代码位置进行检查。

图四

代码优化

优化点一

import com.huaban.analysis.jieba.JiebaSegmenter.SegMode
import com.huaban.analysis.jieba.{JiebaSegmenter, SegToken}

val wordList:Array[String] =  sc.textFile(path).collect()

val srcDF: DataFrame = hiveContext.sql(sql).mapPartitions(partition => {
    val buffer = new ListBuffer[(String, String)]
    val jiebaSegmenter = new JiebaSegmenter()

    val it = partition
    while (it.hasNext) {
        val row = it.next()
        val appid = row.getLong(0).toString
        val text = jiebaSegmenter
            .process(row.getString(1).toString, SegMode.SEARCH)
            .toArray
            .map(_.asInstanceOf[SegToken].word)
            .filter(x => !wordList.contains(x)) // 这里即为83行
            .mkString(" ")
        buffer.append((appid, text))
    }
    buffer.iterator
})

优化后

import com.huaban.analysis.jieba.JiebaSegmenter.SegMode
import com.huaban.analysis.jieba.{JiebaSegmenter, SegToken}

val wordList =  sc.textFile(path).collect().toSet // 将Array转换为Set
val wordList_bc = sc.broadcast(frequencyFilterWordList)	// 使用广播变量
val srcDF: DataFrame = hiveContext.sql(sql).mapPartitions(partition => {
    val buffer = new ListBuffer[(String, String)]
    val jiebaSegmenter = new JiebaSegmenter()

    val it = partition
    while (it.hasNext) {
        val row = it.next()
        val appid = row.getLong(0).toString
        val text = jiebaSegmenter
            .process(row.getString(1).toString, SegMode.SEARCH)
            .toArray
            .map(_.asInstanceOf[SegToken].word)
            .filter(x => !wordList_bc.value.contains(x)) // 使用Set类型的广播变量
            .mkString(" ")
        buffer.append((appid, text))
    }
    buffer.iterator
})

原因分析:

  1. Set的contains操作时间复杂度低于Array。
  2. 通过使用广播变量,节省在不同task之间的拷贝开销。

优化点二

import org.apache.spark.mllib.linalg.{SparseVector, Vector, Vectors}

def cosinSimilarity(v1: SparseVector, v2: SparseVector): Double = {
    val indices2: Array[Int] = v2.indices
    
    var sum = 0.0
    for (index <- v1.indices) { // 这里即为40行
        if (indices2.contains(index)) {
            sum += v1(index) * v2(index)
        }
    }
    sum / (Vectors.norm(v1, 2) * Vectors.norm(v2, 2))
}

优化后

import org.apache.spark.mllib.linalg.{SparseVector, Vector, Vectors}

def cosinSimilarity(v1: SparseVector, v2: SparseVector): Double = {
    val indices2: Set[Int] = v2.indices.toSet	// 将indices2由Array转换为Set
    val indices1: Set[Int] = v1.indices.toSet
    
    var sum = 0.0
    for (index <- indices1) {	// 将v1.indices改写为Set类型的indices1
        if (indices2.contains(index)) {
            sum += v1(index) * v2(index)
        }
    }
    sum / (Vectors.norm(v1, 2) * Vectors.norm(v2, 2))
}

原因分析:

  1. Set的contains操作时间复杂度低于Array。
  2. 避免每次循环都进行v1.indices操作。如果向量的长度很大,就能节省很多indices的计算开销。

效果

优化前4小时都不出结果,优化后不到30分钟即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值