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


16

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를 보았고 통찰력을 제공하지 않았다 (또는 적어도 그들에 근거한 설명을 할 수있는 노하우가 부족하다).

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


의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

1
파이썬 블록이 더 빠른 특정 위치가 있는지 확인하기 위해 해당 블록과 명령문의 타이밍을 지정하여 무엇이든 묻기 전에 분석을 시작했을 것입니다. 그렇다면 '이 파이썬 문장이 왜 더 빠릅니까?'라는 질문을 선명하게 만들었을 것입니다.
Terry 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}")

일치하는 패턴과 일치하지 않는 패턴이 혼합 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와 동일하므로 일정한 오버 헤드로 간주 될 수 있습니다. 또한 이와 같은 간단한 작업조차도 대량 처리 로 적합하지 않습니다 .

3
문제의 훌륭한 접근. 이것을 공유해 주셔서 감사합니다
Alexandros Biratsis

1
@egordoe Alexandros는 "Python이 호출되지 않았습니다"라는 것이 아니라 "여기에 UDF가 호출되지 않았습니다"라고 말했습니다. 직렬화 오버 헤드는 시스템간에 데이터가 교환되는 경우 (즉, 데이터를 UDF로 전달하거나 다시 전달하려는 경우) 중요합니다.
user10938362

1
@egordoe 직렬화 오버 헤드라는 두 가지를 혼동합니다. 이는 사소한 객체가 앞뒤로 전달되는 문제입니다. 그리고 커뮤니케이션의 오버 헤드. 바이트 문자열을 전달하고 디코딩하기 때문에 직렬화 오버 헤드가 거의 없거나 전혀 없으며 파티션 당 단일 정수를 얻으므로 대부분 방향으로 발생합니다. 통신은 일부 우려 사항이지만 로컬 소켓을 통해 데이터를 전달하면 프로세스 간 통신과 관련하여 실제로 얻을 수 있으므로 효율적입니다. 그것이 확실하지 않으면 소스를 읽는 것이 좋습니다. 어렵지 않으며 깨달을 것입니다.
user10938362

1
또한 직렬화 방법은 동일하지 않습니다. Spark 사례에서 우수한 직렬화 방법을 사용하면 더 이상 걱정하지 않는 수준으로 비용을 절감 할 수 있으며 (화살표가있는 Pandas UDF 참조) 다른 요인이 우세 할 수 있습니다 (예 : Scala 창 기능과 Pandas와 동등한 기능의 성능 비교 참조) UDF-파이썬은이 질문보다 훨씬 높은 마진으로 승리합니다).
user10938362

1
그리고 요점은 @ Jasper-M입니까? 개별 Spark 작업은 일반적으로 이와 비슷한 작업을 수행 할 수있을 정도로 작습니다. 나에게 잘못된 길을 택하지 마십시오.이 질문이나 전체 질문을 무효화하는 실제 반대 사례가있는 경우 게시하십시오. 이미 2 차 조치가이 가치에 어느 정도 기여하지만 비용을 지배하지는 않습니다. 우리는 여기에 모든 엔지니어 (일종의 엔지니어)입니다. 믿음이 아닌 숫자와 코드를 말합시다.
user10938362

4

스칼라 작업은 구성이 잘못되어 시간이 오래 걸리므로 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 배 더 빠를 것입니다.


두 작업이 동시에 32 개의 작업을 실행한다는 것을 확인 했으므로 이것이 범인이라고 생각하지 않습니까?
maestromusica

편집 해 주셔서 감사합니다. 지금 테스트 해보십시오
maestromusica

안녕하세요 @maestromusica 이것은 본질적으로 파이썬 이이 특정 사용 사례에서 스칼라보다 성능이 우수하지 않을 수 있기 때문에 리소스 구성에 있어야합니다. 또 다른 이유는 상관 관계가없는 임의의 요인, 즉 특정 순간에 클러스터의 부하 및 유사한 것일 수 있습니다. Btw, 어떤 모드를 사용하십니까? 독립형, 지역, 원사?
egordoe

예,이 답변이 잘못되었음을 확인했습니다. 런타임은 동일합니다. 또한 두 경우 모두 구성을 인쇄했으며 동일합니다.
maestromusica

1
당신이 옳을 것 같아요 이 질문에 코드의 실수와 같은 다른 모든 가능성을 조사하거나 어쩌면 내가 잘못 이해했을 수도 있습니다. 입력 해 주셔서 감사합니다.
maestromusica
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.