Apache Spark:map与mapPartitions?


133

RDD mapmapPartitions方法之间有什么区别?并且flatMap表现得像map还是喜欢mapPartitions?谢谢。

(edit),即两者之间在语义上或执行上有什么区别

  def map[A, B](rdd: RDD[A], fn: (A => B))
               (implicit a: Manifest[A], b: Manifest[B]): RDD[B] = {
    rdd.mapPartitions({ iter: Iterator[A] => for (i <- iter) yield fn(i) },
      preservesPartitioning = true)
  }

和:

  def map[A, B](rdd: RDD[A], fn: (A => B))
               (implicit a: Manifest[A], b: Manifest[B]): RDD[B] = {
    rdd.map(fn)
  }

3
阅读下面的答案后,你可以看看[这方面的经验]被人谁实际使用它的共享。(bzhangusc.wordpress.com/2014/06/19/...bzhangusc.wordpress.com/2014/06/19 /…
Abhidemon '16

Answers:


121

RDD的map和mapPartitions方法之间有什么区别?

方法映射通过应用函数将源RDD的每个元素转换为结果RDD的单个元素。mapPartitions将源RDD的每个分区转换为结果的多个元素(可能没有)。

flatMap的行为类似于map还是mapPartitions?

flatMap都不对单个元素(如map)工作,也不产生结果的多个元素(如mapPartitions)。


3
谢谢-这样,地图会导致随机播放(或更改分区数量)吗?它会在节点之间移动数据吗?我一直在使用mapPartitions来避免在节点之间移动数据,但是不确定flappMap是否会这样做。
尼古拉斯·怀特

如果你看一下源- github.com/apache/incubator-spark/blob/...github.com/apache/incubator-spark/blob/... -无论是mapflatMap具有完全相同的分区,父。
Alexey Romanov 2014年

13
值得注意的是,在2013年旧金山Spark峰会上,一位演讲者提供的演示文稿(goo.gl/JZXDCR)强调指出,与map转换相比,mapPartition的每笔记录开销高的任务执行得更好。根据演示文稿,这是由于设置新任务的成本很高。
Mikel Urkia 2014年

1
我看到的是相反的情况-即使操作非常小,调用mapPartitions的速度也比调用map的迭代速度快。我假设这只是启动将处理地图任务的语言引擎的开销。(我在R中,这可能会有更多的启动开销。)如果要执行多个操作,则mapPartitions似乎要快得多-我假设这是因为它只读取RDD一次。即使将RDD缓存在RAM中,也可以节省类型转换的大量开销。
鲍勃

3
map基本上可以发挥您的功能f,并将其传递到iter.map(f)。因此,基本上它是一种方便的包装方法mapPartitions。如果纯地图样式转换作业(即功能相同)的任何一种方式都具有性能优势,如果您需要创建一些对象进行处理,并且可以共享这些对象,那我将感到惊讶mapPartitions
NightWolf 2015年

129

曝光 小费 :

每当您进行重量级初始化时,应对多个RDD元素进行一次初始化,而不是对每个RDD元素进行一次,并且这种初始化(例如从第三方库创建对象)不能被序列化(以便Spark可以跨集群将其传输到工作节点),请使用mapPartitions()代替 map()mapPartitions()提供了每个工作任务/线程/分区执行一次初始化的操作,而不是每个RDD数据元素执行一次的初始化操作:例如,请参见下文。

val newRd = myRdd.mapPartitions(partition => {
  val connection = new DbConnection /*creates a db connection per partition*/

  val newPartition = partition.map(record => {
    readMatchingFromDB(record, connection)
  }).toList // consumes the iterator, thus calls readMatchingFromDB 

  connection.close() // close dbconnection here
  newPartition.iterator // create a new iterator
})

Q2。flatMap循规蹈矩像地图或类似mapPartitions

是。请参阅示例2的flatmap说明。

Q1。RDD map和RDD有什么区别mapPartitions

map在每个元素级别上 mapPartitions工作的功能,而在分区级别上工作的功能。

示例场景 如果在特定RDD分区中有100K个元素,则在使用时,将触发映射转换使用的函数100K次map

相反,如果使用,mapPartitions那么我们只会调用一次特定的函数,但是我们将传递所有100K记录,并在一次函数调用中获取所有响应。

由于map在一个特定函数上工作了很多次,因此性能将得到提高,尤其是如果函数每次都执行昂贵的工作,而如果一次传递所有元素(在的情况下mappartitions)则不需要这样做。

地图

在RDD的每个项目上应用转换函数,并将结果作为新的RDD返回。

列表变体

def map [U:ClassTag](f:T => U):RDD [U]

范例:

val a = sc.parallelize(List("dog", "salmon", "salmon", "rat", "elephant"), 3)
 val b = a.map(_.length)
 val c = a.zip(b)
 c.collect
 res0: Array[(String, Int)] = Array((dog,3), (salmon,6), (salmon,6), (rat,3), (elephant,8)) 

mapPartitions

这是一个专门的映射,每个分区仅被调用一次。各个分区的全部内容可通过输入参数(Iterarator [T])作为值的顺序流使用。自定义函数必须返回另一个Iterator [U]。合并的结果迭代器将自动转换为新的RDD。请注意,由于选择的分区,以下结果缺少元组(3,4)和(6,7)。

preservesPartitioning指示输入函数是否保留分区程序,false除非这是一对RDD并且输入函数不修改键,否则应该保留。

列表变体

def mapPartitions [U:ClassTag](f:Iterator [T] => Iterator [U],preservesPartitioning:Boolean = false):RDD [U]

例子1

val a = sc.parallelize(1 to 9, 3)
 def myfunc[T](iter: Iterator[T]) : Iterator[(T, T)] = {
   var res = List[(T, T)]()
   var pre = iter.next
   while (iter.hasNext)
   {
     val cur = iter.next;
     res .::= (pre, cur)
     pre = cur;
   }
   res.iterator
 }
 a.mapPartitions(myfunc).collect
 res0: Array[(Int, Int)] = Array((2,3), (1,2), (5,6), (4,5), (8,9), (7,8)) 

例子2

val x = sc.parallelize(List(1, 2, 3, 4, 5, 6, 7, 8, 9,10), 3)
 def myfunc(iter: Iterator[Int]) : Iterator[Int] = {
   var res = List[Int]()
   while (iter.hasNext) {
     val cur = iter.next;
     res = res ::: List.fill(scala.util.Random.nextInt(10))(cur)
   }
   res.iterator
 }
 x.mapPartitions(myfunc).collect
 // some of the number are not outputted at all. This is because the random number generated for it is zero.
 res8: Array[Int] = Array(1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 7, 7, 7, 9, 9, 10) 

上面的程序也可以使用flatMap编写,如下所示。

示例2使用平面图

val x  = sc.parallelize(1 to 10, 3)
 x.flatMap(List.fill(scala.util.Random.nextInt(10))(_)).collect

 res1: Array[Int] = Array(1, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10) 

结论:

mapPartitions转换比map调用函数一次/分区而不是一次/元素快得多。

进一步阅读:foreach与foreachPartitions何时使用什么?


4
我知道您可以使用mapmapPartitions获得相同的结果(请参阅问题中的两个示例);这个问题是关于为什么您会选择一种方法而不是另一种方法。其他答案中的评论真的很有用!另外,您没有提到它mapflatMap传递falsepreservesPartitioning,它的含义是什么。
Nicholas White

2
每次执行的函数与一次执行的函数对分区的执行是我所缺少的链接。使用mapPartition一次访问多个数据记录是一件无价的事情。欣赏答案
分号和胶带

1
有没有map比这更好的方案mapPartitions?如果mapPartitions效果很好,为什么它不是默认的地图实现?
ruhong

1
@oneleggedmule:两者都针对不同的需求,如果您要实例化昂贵的资源(如db连接)(如上例所示),则mappartitions是正确的方法,因为每个分区一个连接,因此我们必须明智地使用它。另请参见
Ram Ghadiyaram

@oneleggedmule在我看来,map()更容易理解和学习,它也是许多不同语言的通用方法。如果开始时不熟悉此Spark特定方法,则它可能比mapPartitions()更容易使用。如果没有性能差异,那么我更喜欢使用map()。
雷蒙德·陈

15

地图

  1. 它一次处理一行,与MapReduce的map()方法非常相似。
  2. 您需要在每一行之后从转换中返回。

MapPartitions

  1. 它可以一次性处理整个分区。
  2. 处理整个分区后,只能从函数返回一次。
  3. 所有中间结果都需要保存在内存中,直到您处理整个分区为止。
  4. 为您提供MapReduce的setup()map()和cleanup()函数

Map Vs mapPartitions http://bytepadding.com/big-data/spark/spark-map-vs-mappartitions/

Spark Map http://bytepadding.com/big-data/spark/spark-map/

Spark mapPartitions http://bytepadding.com/big-data/spark/spark-mappartitions/


关于2-如果您要执行迭代器到迭代器的转换,而不是将迭代器实现为某种类型的集合,则不必将整个分区保留在内存中,实际上,spark可以将分区的一部分溢出到磁盘上。
ilcord

4
您不必将整个分区都保留在内存中,但需要保留结果。您必须先处理整个分区,然后才能返回结果
KrazyGautam,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.