Spark: ทำไมงูใหญ่ถึงมีประสิทธิภาพเหนือกว่า Scala ในกรณีที่ใช้งานของฉัน?


16

เพื่อเปรียบเทียบประสิทธิภาพของ Spark เมื่อใช้ Python และ Scala ฉันสร้างงานเดียวกันทั้งสองภาษาและเปรียบเทียบรันไทม์ ฉันคาดว่างานทั้งสองจะใช้เวลาเท่ากัน แต่งาน Python ใช้เวลาเท่านั้น27minในขณะที่งาน Scala ใช้เวลา37min(เกือบ 40% อีกต่อไป!) ฉันใช้งานเดียวกันใน Java เช่นกันและก็ใช้37minutesด้วย วิธีนี้เป็นไปได้อย่างไรที่ Python เร็วขึ้นมาก?

ตัวอย่างที่ตรวจสอบได้น้อยที่สุด:

งาน Python:

# Configuration
conf = pyspark.SparkConf()
conf.set("spark.hadoop.fs.s3a.aws.credentials.provider", "org.apache.hadoop.fs.s3a.AnonymousAWSCredentialsProvider")
conf.set("spark.executor.instances", "4")
conf.set("spark.executor.cores", "8")
sc = pyspark.SparkContext(conf=conf)

# 960 Files from a public dataset in 2 batches
input_files = "s3a://commoncrawl/crawl-data/CC-MAIN-2019-35/segments/1566027312025.20/warc/CC-MAIN-20190817203056-20190817225056-00[0-5]*"
input_files2 = "s3a://commoncrawl/crawl-data/CC-MAIN-2019-35/segments/1566027312128.3/warc/CC-MAIN-20190817102624-20190817124624-00[0-3]*"

# Count occurances of a certain string
logData = sc.textFile(input_files)
logData2 = sc.textFile(input_files2)
a = logData.filter(lambda value: value.startswith('WARC-Type: response')).count()
b = logData2.filter(lambda value: value.startswith('WARC-Type: response')).count()

print(a, b)

งาน Scala:

// Configuration
config.set("spark.executor.instances", "4")
config.set("spark.executor.cores", "8")
val sc = new SparkContext(config)
sc.setLogLevel("WARN")
sc.hadoopConfiguration.set("fs.s3a.aws.credentials.provider", "org.apache.hadoop.fs.s3a.AnonymousAWSCredentialsProvider")

// 960 Files from a public dataset in 2 batches 
val input_files = "s3a://commoncrawl/crawl-data/CC-MAIN-2019-35/segments/1566027312025.20/warc/CC-MAIN-20190817203056-20190817225056-00[0-5]*"
val input_files2 = "s3a://commoncrawl/crawl-data/CC-MAIN-2019-35/segments/1566027312128.3/warc/CC-MAIN-20190817102624-20190817124624-00[0-3]*"

// Count occurances of a certain string
val logData1 = sc.textFile(input_files)
val logData2 = sc.textFile(input_files2)
val num1 = logData1.filter(line => line.startsWith("WARC-Type: response")).count()
val num2 = logData2.filter(line => line.startsWith("WARC-Type: response")).count()

println(s"Lines with a: $num1, Lines with b: $num2")

เพียงแค่ดูที่รหัสพวกเขาดูเหมือนจะเหมือนกัน ฉันดู DAGs และพวกเขาไม่ได้ให้ข้อมูลเชิงลึกใด ๆ (หรืออย่างน้อยฉันก็ขาดความรู้ที่จะอธิบายด้วยคำอธิบายเหล่านั้น)

ฉันจะขอบคุณพอยน์เตอร์ใด ๆ


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

1
ฉันจะเริ่มต้นการวิเคราะห์ก่อนถามอะไรโดยกำหนดเวลาบล็อกและข้อความที่เกี่ยวข้องเพื่อดูว่ามีสถานที่เฉพาะที่เวอร์ชันงูใหญ่นั้นเร็วกว่านี้หรือไม่ จากนั้นคุณอาจจะสามารถแปลงความคมชัดของคำถามที่ว่า
เทอร์รี่ Jan Reedy

คำตอบ:


11

สมมติฐานพื้นฐานของคุณว่า Scala หรือ Java ควรเร็วกว่าสำหรับงานเฉพาะนี้ไม่ถูกต้อง คุณสามารถตรวจสอบได้อย่างง่ายดายด้วยแอพพลิเคชั่นในตัว สกาล่าที่หนึ่ง:

import scala.io.Source
import java.time.{Duration, Instant}

object App {
  def main(args: Array[String]) {
    val Array(filename, string) = args

    val start = Instant.now()

    Source
      .fromFile(filename)
      .getLines
      .filter(line => line.startsWith(string))
      .length

    val stop = Instant.now()
    val duration = Duration.between(start, stop).toMillis
    println(s"${start},${stop},${duration}")
  }
}

งูหลามหนึ่ง

import datetime
import sys

if __name__ == "__main__":
    _, filename, string = sys.argv
    start = datetime.datetime.now()
    with open(filename) as fr:
        # Not idiomatic or the most efficient but that's what
        # PySpark will use
        sum(1 for _ in filter(lambda line: line.startswith(string), fr))

    end = datetime.datetime.now()
    duration = round((end - start).total_seconds() * 1000)
    print(f"{start},{end},{duration}")

ผลลัพธ์ (300 ซ้ำแต่ละ Python 3.7.6, Scala 2.11.12), Posts.xmlจากhermeneutics.stackexchange.com data dumpด้วยการผสมผสานของการจับคู่และรูปแบบที่ไม่ตรงกัน:

boxplots ของ durartion หน่วยเป็นมิลลิวินาทีสำหรับโปรแกรมด้านบน

  • Python 273.50 (258.84, 288.16)
  • สกาล่า 634.13 (533.81, 734.45)

อย่างที่คุณเห็น Python ไม่เพียง แต่เป็นระบบที่เร็วขึ้นเท่านั้น แต่ยังมีความสอดคล้องมากกว่า (สเปรดต่ำกว่า)

ข้อความนำไปใช้คือ - อย่าเชื่อFUD ที่ไม่พร้อมเพรียง- ภาษาอาจเร็วขึ้นหรือช้าลงในงานที่เฉพาะเจาะจงหรือกับสภาพแวดล้อมที่เฉพาะเจาะจง (เช่นที่นี่ Scala สามารถถูกโจมตีได้โดยการเริ่มต้น JVM และ / หรือ GC และ / หรือ JIT) เช่น "XYZ เร็วกว่า X4" หรือ "XYZ ช้ากว่าเมื่อเทียบกับ ZYX (.. ) ประมาณช้าลง 10 เท่า" มักจะหมายความว่ามีคนเขียนโค้ดที่ไม่ดีจริงๆเพื่อทดสอบสิ่งต่าง ๆ

แก้ไข :

เพื่อแก้ไขข้อกังวลบางประการที่เกิดขึ้นในความคิดเห็น:

  • ในข้อมูลรหัส OP จะถูกส่งผ่านไปในทิศทางเดียว (JVM -> Python) และไม่จำเป็นต้องมีการทำให้เป็นอนุกรมจริง (เส้นทางที่เฉพาะเจาะจงนี้เพิ่งผ่านการทดสอบตามที่เป็นอยู่และถอดรหัส UTF-8 ในด้านอื่น ๆ ) นั่นคือราคาถูกที่สุดเท่าที่จะได้รับเมื่อพูดถึง "การทำให้เป็นอันดับ"
  • สิ่งที่ส่งกลับเป็นเพียงจำนวนเต็มเดียวโดยพาร์ติชันดังนั้นในทิศทางที่ส่งผลกระทบเล็กน้อย
  • การสื่อสารเสร็จสิ้นผ่านโลคัลซ็อกเก็ต (การสื่อสารทั้งหมดกับผู้ปฏิบัติงานที่นอกเหนือจากการเชื่อมต่อเริ่มต้นและดำเนินการโดยใช้file descriptor ที่ส่งคืนจากlocal_connect_and_authและไม่มีอะไรอื่นนอกจากไฟล์ที่เกี่ยวข้องกับซ็อกเก็ต ) อีกครั้งราคาถูกเท่าที่ได้รับเมื่อพูดถึงการสื่อสารระหว่างกระบวนการ
  • เมื่อพิจารณาถึงความแตกต่างของประสิทธิภาพดิบที่แสดงด้านบน (สูงกว่าที่คุณเห็นในโปรแกรมของคุณ) มีอัตรากำไรขั้นต้นสำหรับค่าโสหุ้ยที่ระบุไว้ด้านบน
  • กรณีนี้แตกต่างอย่างสิ้นเชิงจากกรณีที่วัตถุเรียบง่ายหรือซับซ้อนต้องถูกส่งผ่านไปยังและจาก Python interpreter ในรูปแบบที่สามารถเข้าถึงได้สำหรับทั้งสองฝ่ายในฐานะที่ทิ้งขยะที่เข้ากันได้กับดอง (ตัวอย่างที่เด่นที่สุด ได้แก่ UDF แบบเก่าบางส่วน - สไตล์ MLLib)

แก้ไข 2 :

เนื่องจากjasper-mมีความกังวลเกี่ยวกับค่าใช้จ่ายในการเริ่มต้นที่นี่ใครสามารถพิสูจน์ได้ว่า Python ยังมีข้อได้เปรียบที่สำคัญกว่า Scala แม้ว่าขนาดอินพุตจะเพิ่มขึ้นอย่างมีนัยสำคัญ

นี่คือผลลัพธ์สำหรับ 2003360 บรรทัด / 5.6G (อินพุตเดียวกัน, ทำซ้ำหลายครั้ง, 30 ครั้ง) ซึ่งวิธีใดเหนือสิ่งอื่นใดที่คุณคาดหวังในงาน Spark เดียว

ป้อนคำอธิบายรูปภาพที่นี่

  • Python 22809.57 (21466.26, 24152.87)
  • สกาล่า 27315.28 (24367.24, 30263.31)

โปรดทราบช่วงความมั่นใจที่ไม่ทับซ้อนกัน

แก้ไข 3 :

หากต้องการแก้ไขความคิดเห็นอื่นจาก Jasper-M:

การประมวลผลจำนวนมากยังคงเกิดขึ้นภายใน JVM ในเคส Spark

นั่นไม่ถูกต้องในกรณีนี้โดยเฉพาะ:

  • งานที่ต้องถามคืองานแผนที่ที่มีการลดระดับโลกเพียงครั้งเดียวโดยใช้ PySpark RDDs
  • PySpark RDD (ต่างจากสมมติว่าDataFrame) ใช้การทำงานขั้นต้นใน Python โดยมีข้อยกเว้นอินพุตเอาต์พุตและการสื่อสารระหว่างโหนด
  • เนื่องจากเป็นงานขั้นตอนเดียวและเอาต์พุตสุดท้ายมีขนาดเล็กพอที่จะเพิกเฉยได้ความรับผิดชอบหลักของ JVM (ถ้ามีให้กับ nitpick การดำเนินการนี้ส่วนใหญ่ใน Java ไม่ใช่ Scala) คือการเรียกใช้รูปแบบอินพุต Hadoop และผลักข้อมูลผ่านซ็อกเก็ต ไฟล์ไปยัง Python
  • ส่วนการอ่านจะเหมือนกันสำหรับ JVM และ Python API ดังนั้นจึงถือได้ว่าเป็นค่าใช้จ่ายคงที่ มันไม่ได้มีคุณสมบัติเป็นกลุ่มของการประมวลผลแม้สำหรับงานง่าย ๆ เช่นนี้

3
แนวทางที่ยอดเยี่ยมของปัญหา ขอบคุณสำหรับการแบ่งปัน
Alexandros Biratsis

1
@egordoe Alexandros กล่าวว่า "ไม่มี UDF ที่เรียกใช้ที่นี่" ไม่ใช่ว่า "Python ไม่ได้เรียกใช้" - นั่นทำให้เกิดความแตกต่าง ค่าใช้จ่ายในการทำให้เป็นอันดับเป็นสิ่งสำคัญที่ข้อมูลจะถูกแลกเปลี่ยนระหว่างระบบ (เช่นเมื่อคุณต้องการส่งผ่านข้อมูลไปยัง UDF และย้อนกลับ)
user10938362

1
@egordoe คุณสับสนสองสิ่งอย่างชัดเจน - ค่าใช้จ่ายในการทำให้เป็นอนุกรมซึ่งเป็นปัญหาที่วัตถุที่ไม่สำคัญถูกส่งผ่านไปมา และค่าใช้จ่ายในการสื่อสาร มีค่าใช้จ่ายในการทำให้เป็นอนุกรมน้อยหรือไม่มีเลยเพราะคุณเพิ่งผ่านและถอดรหัส bytestrings และที่เกิดขึ้นในทิศทางที่เป็นส่วนใหญ่เมื่อคุณได้รับจำนวนเต็มเดียวต่อพาร์ติชัน การสื่อสารเป็นเรื่องที่น่ากังวล แต่การส่งข้อมูลผ่านซ็อกเก็ตในท้องถิ่นนั้นมีประสิทธิภาพตามที่ได้รับจริง ๆ เมื่อมาถึงการสื่อสารระหว่างกระบวนการ หากยังไม่ชัดเจนฉันขอแนะนำให้อ่านแหล่งที่มา - มันไม่ยากและจะสว่าง
user10938362

1
นอกจากนี้วิธีการทำให้เป็นอันดับไม่เพียงทำให้เท่าเทียมกัน เนื่องจากกรณี Spark แสดงวิธีการจัดลำดับที่ดีสามารถลดค่าใช้จ่ายให้อยู่ในระดับที่ไม่ต้องกังวลอีกต่อไป (ดู Pandas UDF พร้อมลูกศร) และเมื่อมันเกิดขึ้นปัจจัยอื่น ๆ สามารถครอบงำได้ (ดูตัวอย่างการเปรียบเทียบประสิทธิภาพระหว่างฟังก์ชั่นหน้าต่าง Scala UDFs - Python ชนะมาร์จิ้นที่สูงกว่านั้นมากกว่าในคำถามนี้)
user10938362

1
และประเด็นของคุณคือ @ Jasper-M? งาน Spark ส่วนบุคคลมักจะมีขนาดเล็กพอที่จะมีภาระงานเทียบเคียงกับสิ่งนี้ อย่าพาฉันไปผิดทาง แต่ถ้าคุณมีตัวอย่างจริงที่ทำให้โมเดอเรเตอร์นี้หรือคำถามทั้งหมดกรุณาโพสต์ ฉันได้ตั้งข้อสังเกตแล้วว่าการกระทำที่สองมีส่วนช่วยในระดับนี้ แต่พวกเขาไม่ได้ควบคุมค่าใช้จ่าย พวกเราทุกคนเป็นวิศวกร (อยู่ที่นี่) - มาคุยกับหมายเลขและรหัสไม่ใช่ความเชื่อ
user10938362

4

งาน Scala ใช้เวลานานขึ้นเนื่องจากมีการกำหนดค่าผิดพลาดดังนั้นงาน Python และ Scala จึงได้รับการจัดหาทรัพยากรที่ไม่เท่ากัน

โค้ดมีข้อผิดพลาดสองประการ:

val sc = new SparkContext(config) // LINE #1
sc.setLogLevel("WARN")
sc.hadoopConfiguration.set("fs.s3a.aws.credentials.provider", "org.apache.hadoop.fs.s3a.AnonymousAWSCredentialsProvider")
sc.hadoopConfiguration.set("spark.executor.instances", "4") // LINE #4
sc.hadoopConfiguration.set("spark.executor.cores", "8") // LINE #5
  1. บรรทัดที่ 1. เมื่อดำเนินการบรรทัดแล้วการกำหนดค่าทรัพยากรของงาน Spark ได้รับการจัดตั้งและแก้ไขแล้ว จากจุดนี้ไปไม่สามารถปรับอะไรได้อีก ไม่ใช่จำนวนของตัวเรียกใช้งานหรือจำนวนของแกนประมวลผลต่อตัวเรียกทำงาน
  2. สาย 4-5 sc.hadoopConfigurationผิดที่ในการตั้งค่า Spark ใด ๆ มันควรจะตั้งอยู่ในเช่นคุณส่งผ่านไปยังconfignew SparkContext(config)

[เพิ่ม] แบริ่งข้างต้นในใจฉันจะเสนอให้เปลี่ยนรหัสของงานสกาล่าเป็น

config.set("spark.executor.instances", "4")
config.set("spark.executor.cores", "8")
val sc = new SparkContext(config) // LINE #1
sc.setLogLevel("WARN")
sc.hadoopConfiguration.set("fs.s3a.aws.credentials.provider", "org.apache.hadoop.fs.s3a.AnonymousAWSCredentialsProvider")

และทดสอบอีกครั้ง ฉันพนันได้เลยว่าเวอร์ชั่น Scala จะเร็วขึ้นเป็นเท่าตัว


ฉันตรวจสอบแล้วว่างานทั้งสองทำงาน 32 งานพร้อมกันดังนั้นฉันไม่คิดว่านี่เป็นผู้ร้ายหรือไม่
maestromusica

ขอบคุณสำหรับการแก้ไขจะพยายามทดสอบตอนนี้
maestromusica

สวัสดี @maestromusica มันจะต้องเป็นสิ่งที่อยู่ในการกำหนดค่าทรัพยากรเพราะภายใน Python อาจไม่ดีกว่า Scala ในกรณีการใช้งานนี้โดยเฉพาะ เหตุผลอื่นอาจเป็นปัจจัยสุ่มที่ไม่เกี่ยวข้องเช่นโหลดของคลัสเตอร์ ณ ขณะใดช่วงหนึ่งและใกล้เคียงกัน Btw คุณใช้โหมดไหน แบบสแตนด์อโลนท้องถิ่นเส้นด้าย
egordoe

ใช่ฉันตรวจสอบแล้วว่าคำตอบนี้ไม่ถูกต้อง รันไทม์เหมือนกัน ฉันยังพิมพ์การกำหนดค่าในทั้งสองกรณีและมันเหมือนกัน
maestromusica

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