Apache Spark: map vs mapPartitions?


133

อะไรคือความแตกต่างระหว่างRDD mapและmapPartitionsวิธีการ? และflatMapประพฤติชอบmapหรือmapPartitionsไม่? ขอบคุณ

(แก้ไข) คืออะไรคือความแตกต่าง (ทั้งในเชิงความหมายหรือในแง่ของการดำเนินการ) ระหว่าง

  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

คำตอบ:


121

ความแตกต่างระหว่างแผนที่ของ RDD และเมธอด mapPartitions คืออะไร

แผนผังวิธีการแปลงแต่ละองค์ประกอบของ RDD ต้นทางให้เป็นองค์ประกอบเดียวของผลลัพธ์ RDD โดยใช้ฟังก์ชัน mapPartitionsแปลงแต่ละพาร์ติชันของ RDD ต้นทางเป็นหลายองค์ประกอบของผลลัพธ์ (อาจไม่มี)

และ flatMap ทำงานเหมือนแผนที่หรือเหมือน mapPartitions หรือไม่?

มิใช่flatMapทำงานบนองค์ประกอบเดียว (เป็นmap) และผลิตหลายองค์ประกอบของผล (เป็นmapPartitions)


3
ขอบคุณ - แผนที่ทำให้เกิดการสุ่ม (หรือเปลี่ยนจำนวนพาร์ติชัน) หรือไม่? มันย้ายข้อมูลระหว่างโหนดหรือไม่? ฉันใช้ mapPartitions เพื่อหลีกเลี่ยงการย้ายข้อมูลระหว่างโหนด แต่ไม่แน่ใจว่า flapMap จะทำได้หรือไม่
Nicholas White

หากคุณดูแหล่งที่มา - github.com/apache/incubator-spark/blob/…และgithub.com/apache/incubator-spark/blob/… - ทั้งคู่mapและflatMapมีพาร์ติชันเดียวกันทุกประการกับพาเรนต์
Alexey Romanov

13
หมายเหตุการนำเสนอโดยวิทยากรในงาน 2013 San Francisco Spark Summit (goo.gl/JZXDCR) เน้นว่างานที่มีค่าโสหุ้ยต่อการบันทึกสูงทำงานได้ดีกว่าเมื่อใช้ mapPartition มากกว่าการเปลี่ยนแปลงแผนที่ ตามการนำเสนอเนื่องจากการตั้งค่างานใหม่มีค่าใช้จ่ายสูง
Mikel Urkia

1
ฉันเห็นสิ่งที่ตรงกันข้าม - แม้จะมีการดำเนินการที่เล็กมาก แต่ก็เรียก mapPartitions ได้เร็วกว่าและทำซ้ำได้มากกว่าแผนที่การโทร ฉันสมมติว่านี่เป็นเพียงค่าใช้จ่ายในการเริ่มต้นเครื่องมือภาษาที่จะประมวลผลงานแผนที่ (ฉันอยู่ใน R ซึ่งอาจมีค่าใช้จ่ายในการเริ่มต้นมากกว่า) หากคุณจะดำเนินการหลายอย่าง mapPartitions ดูเหมือนจะเร็วกว่าเล็กน้อย - ฉันคิดว่านี่เป็นเพราะมันอ่าน RDD เพียงครั้งเดียว แม้ว่า RDD จะถูกแคชไว้ใน RAM แต่ก็ช่วยประหยัดค่าใช้จ่ายจำนวนมากจากการแปลงประเภทได้
Bob

3
mapโดยทั่วไปจะใช้เวลาการทำงานของคุณและผ่านมันเข้าไปf ดังนั้นโดยทั่วไปวิธีการที่สะดวกสบายของตนที่ตัดiter.map(f) mapPartitionsฉันจะแปลกใจถ้ามีข้อได้เปรียบด้านประสิทธิภาพไม่ว่าจะด้วยวิธีใดก็ตามสำหรับงานการแปลงรูปแบบแผนที่บริสุทธิ์ (เช่นซึ่งฟังก์ชันเหมือนกัน) หากคุณต้องการสร้างวัตถุบางอย่างสำหรับการประมวลผลหากสามารถแชร์วัตถุเหล่านี้ได้ก็mapPartitionsจะได้เปรียบ
NightWolf

130

ภูตผีปีศาจ เคล็ดลับ:

เมื่อใดก็ตามที่คุณมีการกำหนดค่าเริ่มต้นที่มีน้ำหนักมากซึ่งควรทำครั้งเดียวสำหรับหลาย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
})

ไตรมาสที่ 2 ไม่flatMapประพฤติเช่นแผนที่หรือชอบmapPartitions?

ใช่. โปรดดูตัวอย่างที่ 2 ของflatmap.. อธิบายตัวเอง

ไตรมาสที่ 1 อะไรคือความแตกต่างระหว่าง RDD mapและmapPartitions

mapทำงานฟังก์ชันที่ใช้ในระดับต่อองค์ประกอบในขณะที่ mapPartitionsออกกำลังกายที่ระดับพาร์ติชัน

สถานการณ์สมมติตัวอย่าง : ถ้าเรามี 100K องค์ประกอบโดยเฉพาะRDDพาร์ทิชันแล้วเราจะปิดไฟฟังก์ชั่นการใช้งานโดยการเปลี่ยนแปลงการทำแผนที่ 100K mapครั้งเมื่อเราใช้

ในทางกลับกันถ้าเราใช้mapPartitionsเราจะเรียกใช้ฟังก์ชันนั้นเพียงครั้งเดียว แต่เราจะส่งผ่านในระเบียน 100K ทั้งหมดและรับการตอบกลับทั้งหมดในการเรียกใช้ฟังก์ชันเดียว

จะมีการเพิ่มประสิทธิภาพเนื่องจากmapทำงานกับฟังก์ชันเฉพาะหลาย ๆ ครั้งโดยเฉพาะอย่างยิ่งหากฟังก์ชันนั้นทำสิ่งที่มีราคาแพงในแต่ละครั้งที่ไม่จำเป็นต้องทำหากเราส่งผ่านองค์ประกอบทั้งหมดในครั้งเดียว (ในกรณีmappartitions)

แผนที่

ใช้ฟังก์ชันการแปลงกับแต่ละรายการของ RDD และส่งคืนผลลัพธ์เป็น RDD ใหม่

รายชื่อตัวแปร

แผนที่ def [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 ใหม่โดยอัตโนมัติ โปรดทราบว่า tuples (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 Vs foreachPartitions เมื่อใดควรใช้ What?


4
ฉันรู้ว่าคุณสามารถใช้mapหรือmapPartitionsเพื่อให้ได้ผลลัพธ์เดียวกัน (ดูสองตัวอย่างในคำถาม) คำถามนี้เกี่ยวกับสาเหตุที่คุณเลือกทางใดทางหนึ่ง ความคิดเห็นในคำตอบอื่นมีประโยชน์จริงๆ! นอกจากนี้คุณไม่ได้พูดถึงสิ่งนั้นmapและflatMapส่งผ่านfalseไปpreservesPartitioningและผลกระทบของสิ่งนั้นคืออะไร
Nicholas White

2
ฟังก์ชันที่เรียกใช้ทุกครั้งกับฟังก์ชันที่เรียกใช้งานหนึ่งครั้งสำหรับพาร์ติชันคือลิงก์ที่ฉันขาดหายไป การเข้าถึงบันทึกข้อมูลมากกว่าหนึ่งครั้งด้วย mapPartition เป็นสิ่งที่ประเมินค่าไม่ได้ ขอบคุณคำตอบ
อัฒภาคและเทปพันท่อเมื่อ

1
มีสถานการณ์ที่mapดีกว่าmapPartitionsหรือไม่? ถ้าmapPartitionsดีมากทำไมไม่ใช้แผนที่เริ่มต้น?
ruhong

1
@oneleggedmule: ทั้งสองอย่างมีความต้องการที่แตกต่างกันซึ่งเราต้องใช้อย่างชาญฉลาดหากคุณกำลังสร้างอินสแตนซ์ทรัพยากรเช่นการเชื่อมต่อ db (ดังที่แสดงในตัวอย่างด้านบน) ซึ่งมีค่าใช้จ่ายสูงดังนั้น mappartitions จึงเป็นแนวทางที่ถูกต้องเนื่องจากการเชื่อมต่อหนึ่งครั้งต่อพาร์ติชัน ยัง saveAsTextFile การแมปพาร์ติชันที่ใช้ภายใน โปรดดู
Ram Ghadiyaram

@oneleggedmule จากมุมมองของฉัน map () นั้นเข้าใจและเรียนรู้ได้ง่ายกว่าและยังเป็นวิธีการทั่วไปของภาษาต่างๆ มันอาจจะใช้ง่ายกว่า mapPartitions () ถ้าใครไม่คุ้นเคยกับวิธีการเฉพาะของ Spark ในตอนแรก หากไม่มีความแตกต่างด้านประสิทธิภาพฉันชอบใช้ map ()
Raymond Chen

15

แผนที่ :

  1. มันประมวลผลทีละแถวคล้ายกับ map () method ของ MapReduce
  2. คุณกลับมาจากการเปลี่ยนแปลงหลังจากทุกแถว

MapPartitions

  1. มันประมวลผลพาร์ติชันที่สมบูรณ์ในครั้งเดียว
  2. คุณสามารถกลับจากฟังก์ชันได้เพียงครั้งเดียวหลังจากประมวลผลพาร์ติชันทั้งหมด
  3. ผลลัพธ์ระดับกลางทั้งหมดจะต้องถูกเก็บไว้ในหน่วยความจำจนกว่าคุณจะประมวลผลพาร์ติชันทั้งหมด
  4. ให้คุณชอบฟังก์ชั่น setup () map () และ cleanup () ของ MapReduce

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 - หากคุณกำลังทำการแปลงตัววนซ้ำไปยังตัววนซ้ำและไม่ได้ทำให้ตัววนซ้ำเป็นชุดของการจัดเรียงบางประเภทคุณจะไม่ต้องถือพาร์ติชันทั้งหมดไว้ในหน่วยความจำอันที่จริงแล้ววิธีนี้จะสามารถจุดประกายได้ หกส่วนของพาร์ติชันไปยังดิสก์
ilcord

4
คุณไม่จำเป็นต้องถือพาร์ติชันทั้งหมดไว้ในหน่วยความจำ แต่ผลลัพธ์ คุณไม่สามารถส่งคืนผลลัพธ์ได้จนกว่าคุณจะประมวลผลพาร์ติชันทั้งหมด
KrazyGautam
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.