스칼라 대 파이썬의 스파크 성능


178

나는 스칼라보다 파이썬을 선호합니다. 그러나 Spark는 기본적으로 스칼라로 작성되었으므로 코드가 스칼라에서 Python 버전보다 더 빨리 실행될 것으로 예상되었습니다.

그 가정으로, 1GB의 데이터에 대해 매우 일반적인 전처리 코드의 스칼라 버전을 배우고 작성하려고 생각했습니다. 데이터는 Kaggle의 SpringLeaf 경쟁에서 선택 됩니다. 데이터에 대한 개요를 제공하기 위해 (1936 차원 및 145232 행 포함). 데이터는 int, float, string, boolean과 같은 다양한 유형으로 구성됩니다. Spark 처리에 8 개 중 6 개 코어를 사용하고 있습니다. 그렇기 minPartitions=6때문에 모든 코어에 처리해야 할 것이 있습니다.

스칼라 코드

val input = sc.textFile("train.csv", minPartitions=6)

val input2 = input.mapPartitionsWithIndex { (idx, iter) => 
  if (idx == 0) iter.drop(1) else iter }
val delim1 = "\001"

def separateCols(line: String): Array[String] = {
  val line2 = line.replaceAll("true", "1")
  val line3 = line2.replaceAll("false", "0")
  val vals: Array[String] = line3.split(",")

  for((x,i) <- vals.view.zipWithIndex) {
    vals(i) = "VAR_%04d".format(i) + delim1 + x
  }
  vals
}

val input3 = input2.flatMap(separateCols)

def toKeyVal(line: String): (String, String) = {
  val vals = line.split(delim1)
  (vals(0), vals(1))
}

val input4 = input3.map(toKeyVal)

def valsConcat(val1: String, val2: String): String = {
  val1 + "," + val2
}

val input5 = input4.reduceByKey(valsConcat)

input5.saveAsTextFile("output")

파이썬 코드

input = sc.textFile('train.csv', minPartitions=6)
DELIM_1 = '\001'


def drop_first_line(index, itr):
  if index == 0:
    return iter(list(itr)[1:])
  else:
    return itr

input2 = input.mapPartitionsWithIndex(drop_first_line)

def separate_cols(line):
  line = line.replace('true', '1').replace('false', '0')
  vals = line.split(',')
  vals2 = ['VAR_%04d%s%s' %(e, DELIM_1, val.strip('\"'))
           for e, val in enumerate(vals)]
  return vals2


input3 = input2.flatMap(separate_cols)

def to_key_val(kv):
  key, val = kv.split(DELIM_1)
  return (key, val)
input4 = input3.map(to_key_val)

def vals_concat(v1, v2):
  return v1 + ',' + v2

input5 = input4.reduceByKey(vals_concat)
input5.saveAsTextFile('output')

스칼라 공연 0 단계 (38 분), 1 단계 (18 초) 여기에 이미지 설명을 입력하십시오

Python Performance Stage 0 (11 분), Stage 1 (7 초) 여기에 이미지 설명을 입력하십시오

둘 다 서로 다른 DAG 시각화 그래프를 생성합니다 (두 그림 모두 Scala ( map) 및 Python ( reduceByKey)에 대해 서로 다른 스테이지 0 함수를 표시하기 때문에 )

그러나 본질적으로 두 코드 모두 데이터를 (dimension_id, 문자열 값 목록) RDD로 변환하여 디스크에 저장하려고합니다. 출력은 각 차원에 대한 다양한 통계를 계산하는 데 사용됩니다.

성능면에서 이와 같은 실제 데이터에 대한 스칼라 코드 는 Python 버전보다 4 배 느리게 실행되는 것 같습니다 . 저에게 희소식은 파이썬을 유지하는 좋은 동기 부여를 주었다는 것입니다. 나쁜 소식은 왜 그런지 이해하지 못했습니까?


8
아마도 이것은 코드와 나는 것으로, 다른 결과를 얻을으로 의존하는 응용 프로그램입니다 아파치 불꽃 파이썬 스칼라보다 느리다는 π의 라이프니츠 식의 억 기간을 합산 할 때

3
재미있는 질문! Btw, 여기도 살펴보십시오. emptypipes.org/2015/01/17/python-vs-scala-vs-spark 코어가 많을수록 언어 간 차이를 덜 볼 수 있습니다.
Markon

답변:


358

코드에 대한 원래 답변은 아래에서 확인할 수 있습니다.


우선, 각각 고유 한 성능 고려 사항이있는 서로 다른 유형의 API를 구별해야합니다.

RDD API

(JVM 기반 오케스트레이션으로 순수한 Python 구조)

이것은 파이썬 코드의 성능과 PySpark 구현의 세부 사항에 가장 큰 영향을받는 구성 요소입니다. 파이썬 성능은 문제가되지 않지만, 고려해야 할 최소한의 요소는 다음과 같습니다.

  • JVM 통신 오버 헤드 실제로 파이썬 실행기와주고받는 모든 데이터는 소켓과 JVM 작업자를 통해 전달되어야합니다. 이것은 비교적 효율적인 로컬 통신이지만 여전히 무료는 아닙니다.
  • 프로세스 기반 실행기 (Python) 대 스레드 기반 (단일 JVM 다중 스레드) 실행기 (Scala). 각 Python 실행 프로그램은 자체 프로세스에서 실행됩니다. 부작용으로, JVM보다 강력한 격리 기능을 제공하고 실행기 수명주기에 대한 일부 제어 기능을 제공하지만 잠재적으로 메모리 사용량이 크게 증가합니다.

    • 인터프리터 메모리 풋 프린트
    • 로드 된 라이브러리의 설치 공간
    • 덜 효율적인 방송 (각 프로세스에는 자체 방송 사본이 필요함)
  • 파이썬 코드 자체의 성능. 일반적으로 스칼라는 파이썬보다 빠르지 만 작업마다 다릅니다. 또한 Numba 와 같은 JIT , C 확장 ( Cython ) 또는 Theano 와 같은 특수 라이브러리를 포함한 여러 옵션이 있습니다 . 마지막으로, 당신은 ML / MLlib (또는 단순히 NumPy와 스택)를 사용하지 않는 경우 , 사용을 고려 PyPy를 대체 통역. SPARK-3094를 참조하십시오 .

  • PySpark 구성은 spark.python.worker.reuse각 작업마다 Python 프로세스를 포크하는 것과 기존 프로세스를 재사용하는 것 중에서 선택할 수있는 옵션을 제공합니다 . 후자의 옵션은 값 비싼 가비지 수집 (체계적인 테스트의 결과보다 더 인상적 임)을 피하는 데 유용하지만, 전자 (기본값)는 값 비싼 방송 및 수입의 경우에 최적입니다.
  • CPython에서 첫 번째 가비지 수집 방법으로 사용되는 참조 횟수는 일반적인 Spark 워크로드 (스트림과 같은 처리, 참조주기 없음)에서 잘 작동하며 긴 GC 일시 중지 위험을 줄입니다.

MLlib

(혼합 된 Python 및 JVM 실행)

기본 고려 사항은 몇 가지 추가 문제와 함께 이전과 거의 동일합니다. MLlib와 함께 사용되는 기본 구조는 일반 Python RDD 객체이지만 모든 알고리즘은 Scala를 사용하여 직접 실행됩니다.

그것은 파이썬 객체를 스칼라 객체로 변환하는 추가 비용과 다른 방법으로 메모리 사용이 증가하고 나중에 다룰 추가 제한 사항을 의미합니다.

현재 (Spark 2.x) RDD 기반 API는 유지 보수 모드이며 Spark 3.0에서 제거 될 예정 입니다.

DataFrame API 및 Spark ML

(드라이버로 제한된 Python 코드로 JVM 실행)

이들은 아마도 표준 데이터 처리 작업에 가장 적합한 선택 일 것입니다. 파이썬 코드는 대부분 드라이버에서 높은 수준의 논리 연산으로 제한되기 때문에 파이썬과 스칼라간에 성능 차이가 없어야합니다.

단 하나의 예외는 스칼라 동등 물보다 훨씬 덜 효율적인 행 단위 파이썬 UDF의 사용입니다. 개선의 여지가 있지만 (Spark 2.0.0에는 상당한 발전이 있었음) 가장 큰 한계는 내부 표현 (JVM)과 Python 인터프리터 사이의 완전한 왕복입니다. 가능하면 내장 식의 구성을 선호해야합니다 ( 예 : Spark 2.0.0에서 Python UDF 동작이 개선되었지만 기본 실행에 비해 여전히 차선책입니다.

이것은 미래에 개선 할 수 대폭의 도입으로 개선 벡터화 UDF를 (SPARK-21190 및 추가 확장) 제로 복사 직렬화와 효율적인 데이터 교환을 위해 화살표 스트리밍을 사용합니다. 대부분의 응용 프로그램에서 보조 오버 헤드는 무시해도됩니다.

또한 사이에 불필요한 데이터 전달을 방지해야 DataFrames하고 RDDs. 파이썬 인터프리터와의 데이터 전송은 말할 것도없고 값 비싼 직렬화 및 역 직렬화가 필요합니다.

Py4J 통화는 대기 시간이 상당히 길다는 점에 주목할 가치가 있습니다. 여기에는 다음과 같은 간단한 호출이 포함됩니다.

from pyspark.sql.functions import col

col("foo")

일반적으로 문제가되지 않지만 (오버 헤드는 일정하고 데이터 양에 의존하지 않습니다) 소프트 실시간 응용 프로그램의 경우 Java 래퍼 캐싱 / 재사용을 고려할 수 있습니다.

GraphX ​​및 Spark 데이터 세트

현재 (Spark 1.6 2.1) 어느 쪽도 PySpark API를 제공하지 않으므로 PySpark가 Scala보다 무한히 나쁘다고 말할 수 있습니다.

GraphX

실제로 GraphX ​​개발은 거의 중단되었으며 프로젝트는 현재 JIRA 티켓이 닫힌 상태로 유지 관리 모드에 있으며 수정되지 않습니다 . GraphFrames 라이브러리는 Python 바인딩을 사용하는 대체 그래프 처리 라이브러리를 제공합니다.

데이터 세트

주관적으로 말하면 Datasets파이썬 에는 정적으로 유형을 지정할 수있는 장소가 많지 않으며 현재 Scala 구현이 너무 단순하더라도과 동일한 성능 이점을 제공하지 않습니다 DataFrame.

스트리밍

지금까지 내가 본 것 중에서 Python보다 Scala를 사용하는 것이 좋습니다. PySpark가 구조화 된 스트림을 지원할 경우 향후 변경 될 수 있지만 현재 Scala API는 훨씬 강력하고 포괄적이며 효율적인 것으로 보입니다. 내 경험은 상당히 제한적입니다.

Spark 2.x의 구조적 스트리밍은 언어 간 격차를 줄이는 것처럼 보이지만 현재는 아직 초기 단계입니다. 그럼에도 불구하고 RDD 기반 API는 이미 Databricks 문서 (액세스 날짜 2017-03-03) 에서 "레거시 스트리밍"으로 참조 되므로 추가 통합 노력을 기대하는 것이 합리적입니다.

비 성능 고려 사항

기능 패리티

모든 Spark 기능이 PySpark API를 통해 노출되는 것은 아닙니다. 필요한 부품이 이미 구현되어 있는지 확인하고 가능한 제한 사항을 이해하십시오.

MLlib 및 유사한 혼합 컨텍스트를 사용할 때 특히 중요합니다 ( 작업에서 Java / Scala 함수 호출 참조 ). 와 같이 PySpark API의 일부를 공정하게하기 위해 mllib.linalgScala보다 포괄적 인 메소드 세트를 제공합니다.

API 디자인

PySpark API는 스칼라 대응 API를 밀접하게 반영하므로 정확히 Pythonic이 아닙니다. 언어간에 매핑하기가 쉽지만 동시에 파이썬 코드는 이해하기가 훨씬 어려울 수 있습니다.

복잡한 아키텍처

PySpark 데이터 흐름은 순수한 JVM 실행에 비해 비교적 복잡합니다. PySpark 프로그램에 대해 추론하거나 디버그하기가 훨씬 어렵습니다. 또한 스칼라와 JVM에 대한 기본적인 이해는 일반적으로 거의 필요합니다.

Spark 2.x 이상

Dataset동결 된 RDD API를 통해 API 로의 지속적인 변화 는 파이썬 사용자에게 기회와 도전을 제공합니다. API의 높은 수준의 부분은 Python에서 노출하기가 훨씬 쉽지만 고급 기능은 직접 사용하기가 거의 불가능합니다 .

게다가 네이티브 파이썬 함수는 SQL 세계에서 계속해서 2 등 시민입니다. 다행스럽게도 향후 Apache Arrow 직렬화를 통해 개선 될 것입니다 ( 현재의 노력collection데이터를 목표로 하지만 UDF serde는 장기적인 목표입니다 ).

Python 코드베이스에 의존하는 프로젝트의 경우 Dask 또는 Ray 와 같은 순수한 Python 대안 이 흥미로운 대안이 될 수 있습니다.

한 대 다른 대일 필요는 없습니다.

Spark DataFrame (SQL, Dataset) API는 PySpark 응용 프로그램에서 Scala / Java 코드를 통합하는 우아한 방법을 제공합니다. DataFrames데이터를 기본 JVM 코드에 노출하고 결과를 다시 읽는 데 사용할 수 있습니다 . 다른 곳에서 몇 가지 옵션을 설명 했으며 Pyspark에서 Scala 클래스를 사용하는 방법 에서 Python-Scala 왕복의 실제 예제를 찾을 수 있습니다 .

사용자 정의 유형을 도입하여 추가 기능을 보강 할 수 있습니다 ( Spark SQL에서 사용자 정의 유형에 대한 스키마를 정의하는 방법 참조 ).


질문에 제공된 코드에 어떤 문제가 있습니까?

(면책 조항 : Pythonista 관점. 스칼라 트릭을 놓친 것 같습니다.)

우선, 코드에는 전혀 이해가되지 않는 부분이 있습니다. 이미 (key, value)쌍을 사용하여 만들었 zipWithIndex거나 enumerate바로 문자열을 만들 때 요점은 무엇입니까? flatMap재귀 적으로 작동하지 않으므로 단순히 튜플을 생성하고 다른 것을 따라 건너 뛸 수 있습니다 map.

내가 문제를 찾는 다른 부분은 reduceByKey입니다. 일반적으로 reduceByKey집계 함수를 적용하면 셔플해야하는 데이터 양을 줄일 수있는 경우에 유용합니다. 단순히 문자열을 연결하기 때문에 여기서 얻을 것이 없습니다. 참조 수와 같은 저수준 항목을 무시하면 전송해야하는 데이터의 양은와 정확히 동일합니다 groupByKey.

일반적으로 나는 그것에 머물지 않을 것이지만, 내가 말할 수있는 한 스칼라 코드의 병목 현상입니다. JVM에서 문자열을 결합하는 것은 비용이 많이 드는 작업입니다 (예 : 스칼라에서 문자열 연결이 Java에서와 같이 비용이 많이 듭니까? 참조 ). 그것은 당신의 코드에서 _.reduceByKey((v1: String, v2: String) => v1 + ',' + v2) 이와 같은 input4.reduceByKey(valsConcat)것이 좋은 생각이 아님을 의미합니다.

groupByKey사용 하지 않으려면와 aggregateByKey함께 사용하십시오 StringBuilder. 이와 비슷한 것이 트릭을 수행해야합니다.

rdd.aggregateByKey(new StringBuilder)(
  (acc, e) => {
    if(!acc.isEmpty) acc.append(",").append(e)
    else acc.append(e)
  },
  (acc1, acc2) => {
    if(acc1.isEmpty | acc2.isEmpty)  acc1.addString(acc2)
    else acc1.append(",").addString(acc2)
  }
)

그러나 나는 그것이 모든 소란의 가치가 있는지 의심합니다.

위의 내용을 염두에두고 다음과 같이 코드를 다시 작성했습니다.

스칼라 :

val input = sc.textFile("train.csv", 6).mapPartitionsWithIndex{
  (idx, iter) => if (idx == 0) iter.drop(1) else iter
}

val pairs = input.flatMap(line => line.split(",").zipWithIndex.map{
  case ("true", i) => (i, "1")
  case ("false", i) => (i, "0")
  case p => p.swap
})

val result = pairs.groupByKey.map{
  case (k, vals) =>  {
    val valsString = vals.mkString(",")
    s"$k,$valsString"
  }
}

result.saveAsTextFile("scalaout")

파이썬 :

def drop_first_line(index, itr):
    if index == 0:
        return iter(list(itr)[1:])
    else:
        return itr

def separate_cols(line):
    line = line.replace('true', '1').replace('false', '0')
    vals = line.split(',')
    for (i, x) in enumerate(vals):
        yield (i, x)

input = (sc
    .textFile('train.csv', minPartitions=6)
    .mapPartitionsWithIndex(drop_first_line))

pairs = input.flatMap(separate_cols)

result = (pairs
    .groupByKey()
    .map(lambda kv: "{0},{1}".format(kv[0], ",".join(kv[1]))))

result.saveAsTextFile("pythonout")

결과

에서는 local[6]모드 (인텔 (R) 제온 (R) CPU E3-1245 V2 @ 3.40GHz) 기가 바이트 걸리는 실행기 당 메모리 (N = 3)과 :

  • 스칼라-평균 : 250.00s, 표준 : 12.49
  • 파이썬-평균 : 246.66 초, stdev : 1.15

나는 그 시간의 대부분이 셔플 링, 직렬화, 직렬화 해제 및 기타 보조 작업에 소비된다는 것을 확신합니다. 재미있게도,이 머신에서 1 분 이내에 동일한 작업을 수행하는 Python의 순진한 단일 스레드 코드가 있습니다.

def go():
    with open("train.csv") as fr:
        lines = [
            line.replace('true', '1').replace('false', '0').split(",")
            for line in fr]
    return zip(*lines[1:])

23
한동안 경험 한 가장 명확하고 포괄적이며 유용한 답변 중 하나입니다. 감사!
etov

당신은 정말 훌륭한 사람입니다!
DennisLi

-4

위의 답변으로 확장-

스칼라는 파이썬과 비교할 때 여러 가지면에서 더 빠르다는 것을 증명하지만 파이썬이 스칼라보다 인기가 높아지는 몇 가지 정당한 이유가 있습니다.

Python for Apache Spark는 배우고 사용하기가 매우 쉽습니다. 그러나 이것이 Pyspark가 Scala보다 더 나은 선택 인 이유는 아닙니다. 더있다.

Python API for Spark는 클러스터에서 느려질 수 있지만 결국 데이터 과학자는 Scala와 비교하여 훨씬 더 많은 작업을 수행 할 수 있습니다. 스칼라의 복잡성은 없습니다. 인터페이스는 간단하고 포괄적입니다.

코드의 가독성, Apache Spark 용 Python API에 대한 유지 관리 및 친숙성에 대해 말하는 것이 Scala보다 훨씬 좋습니다.

Python에는 기계 학습 및 자연 언어 처리와 관련된 여러 라이브러리가 제공됩니다. 이는 데이터 분석을 돕고 훨씬 성숙하고 시간이 많이 걸리는 통계를 제공합니다. 예를 들어, numpy, pandas, scikit-learn, seaborn 및 matplotlib이 있습니다.

참고 : 대부분의 데이터 과학자는 두 API를 모두 사용하는 하이브리드 방식을 사용합니다.

마지막으로 스칼라 커뮤니티는 종종 프로그래머에게 덜 도움이되는 것으로 판명되었습니다. 이것은 파이썬을 매우 귀중한 학습으로 만듭니다. Java와 같이 정적으로 유형이 지정된 프로그래밍 언어에 대한 경험이 충분하다면 Scala를 전혀 사용하지 않을까 걱정할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.