[python] Spark : 유스 케이스에서 Python이 왜 Scala보다 월등히 뛰어 납니까?

Python과 Scala를 사용할 때 Spark의 성능을 비교하기 위해 두 언어로 동일한 작업을 만들고 런타임을 비교했습니다. 두 작업이 거의 같은 시간이 걸리지 만 Python 작업은 시간이 걸렸지 만 27minScala 작업은 37min거의 40 % 더 걸렸습니다 ! Java에서도 동일한 작업을 구현했으며 37minutes너무 오래 걸렸 습니다. 이것이 어떻게 파이썬이 훨씬 빠를 수 있습니까?

최소 검증 가능한 예 :

파이썬 직업 :

# 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)

스칼라 직업 :

// 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")

코드를 살펴보면 동일한 것으로 보입니다. 나는 DAG를 보았고 통찰력을 제공하지 않았다 (또는 적어도 그들에 근거한 설명을 할 수있는 노하우가 부족하다).

나는 정말 어떤 조언을 주셔서 감사합니다.



답변

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}")

일치하는 패턴과 일치하지 않는 패턴이 혼합 Posts.xmlhermeneutics.stackexchange.com 데이터 덤프 에서 결과 (각각 300 회 반복, Python 3.7.6, Scala 2.11.12) :

위 프로그램에 대한 밀리 리안의 멍청한 상자 그림

  • 파이썬 273.50 (258.84, 288.16)
  • 스칼라 634.13 (533.81, 734.45)

보시다시피 파이썬은 체계적으로 빠를뿐만 아니라 더 일관 적입니다 (더 낮은 스프레드).

메시지를 빼내십시오 . 특정 작업이나 특정 환경 (예 : 여기에서 JVM 시작 및 / 또는 GC 및 / 또는 JIT에 의해 스칼라에 영향을 줄 수 있음)에서 입증되지 않은 FUD 언어가 더 빠르거나 느려질 수 있다고 생각 하십시오. “XYZ는 X4가 빠릅니다”또는 “XYZ는 ZYX (..)와 비교할 때 속도가 느립니다 (약 10 배 느림)”와 같이 보통 누군가가 테스트를하기 위해 실제로 나쁜 코드를 작성했음을 의미합니다.

편집 :

의견에서 제기 된 일부 우려 사항을 해결하려면 다음을 수행하십시오.

  • OP 코드에서 데이터는 주로 한 방향 (JVM-> Python)으로 전달되며 실제 직렬화는 필요하지 않습니다 (이 특정 경로는 바이트 문자열을 그대로 전달하고 다른 쪽은 UTF-8로 디코딩합니다). “직렬화”와 관련하여 얻을 수있는만큼 저렴합니다.
  • 다시 전달되는 것은 파티션별로 단일 정수이므로 방향에 미치는 영향은 무시할 수 있습니다.
  • 통신은 로컬 소켓을 통해 수행됩니다 (초기 연결 및 인증에서 작업자의 모든 통신은 에서 반환 된 파일 설명자를 사용하여 수행되며 소켓 관련 파일local_connect_and_auth 이외의 다른 것은 사용하지 않음 ). 다시 말하지만 프로세스 간 통신에있어 비용이 적게 듭니다.
  • 위에 표시된 원시 성능의 차이 (프로그램에 표시되는 것보다 훨씬 높음)를 고려하면 위에 나열된 오버 헤드에 대한 마진이 많이 있습니다.
  • 이 경우는 단순하거나 복잡한 객체를 피클 호환 덤프로 양 당사자가 액세스 할 수있는 형태로 Python 인터프리터로 전달하거나 전달해야하는 경우와는 완전히 다릅니다 (대부분의 주목할만한 예는 구식 UDF, 일부는 구식) -스타일 MLLib).

편집 2 :

jasper-m 은 여기서 시작 비용에 대해 염려 했기 때문에 입력 크기가 크게 증가하더라도 Python이 Scala보다 여전히 상당한 이점을 가지고 있음을 쉽게 증명할 수 있습니다.

다음은 단일 Spark 작업에서 예상 할 수있는 모든 것을 초과하는 2003360 라인 /5.6G (동일한 입력, 여러 번 중복, 30 회 반복)에 대한 결과입니다.

여기에 이미지 설명을 입력하십시오

  • Python 22809.57 (21466.26, 24152.87)
  • 스칼라 27315.28 (24367.24, 30263.31)

겹치지 않는 신뢰 구간에 유의하십시오.

편집 3 :

Jasper-M의 다른 의견 을 해결하려면 :

Spark 처리의 모든 처리는 여전히 JVM 내부에서 진행되고 있습니다.

이 특별한 경우에 그것은 틀린 것입니다 :

  • 해당 작업은 PySpark RDD를 사용하여 단일 전역 축소가있는 맵 작업입니다.
  • PySpark RDD (와 달리 DataFrame)는 예외 입력, 출력 및 노드 간 통신을 통해 Python에서 기본적으로 많은 기능을 구현합니다.
  • 단일 단계 작업이고 최종 출력이 무시할 정도로 작기 때문에 JVM의 주요 책임 (니트 픽을 수행하는 경우 대부분 스칼라가 아닌 Java로 구현 됨) 하둡 입력 형식을 호출하고 소켓을 통해 데이터를 푸시하는 것입니다 파이썬으로 파일.
  • 읽기 부분은 JVM 및 Python API와 동일하므로 일정한 오버 헤드로 간주 될 수 있습니다. 또한 이와 같은 간단한 작업조차도 대량 처리 로 적합하지 않습니다 .

답변

스칼라 작업은 구성이 잘못되어 시간이 오래 걸리므로 Python 및 스칼라 작업에 자원이 같지 않습니다.

코드에는 두 가지 실수가 있습니다.

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. LINE 1. 회선이 실행되면 Spark 작업의 자원 구성이 이미 설정되어 수정되었습니다. 이 시점부터는 아무것도 조정할 수 없습니다. 실행자 수나 실행 자당 코어 수는 없습니다.
  2. 라인 4-5. sc.hadoopConfigurationSpark 구성을 설정하기에 잘못된 위치입니다. config전달한 인스턴스 에서 설정해야합니다 new 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")

다시 테스트하십시오. 스칼라 버전은 이제 X 배 더 빠를 것입니다.


답변