Есть ли какая-то «механическая» разница между использованием select в DataFrame для сбора необходимой информации и сопоставлением каждой строки базового RDD с той же целью?

Под «механическим» я имею в виду механизм, выполняющий операции. Другими словами, детали реализации.

Какой из предложенных двух лучше / производительнее?

df = # create dataframe ...
df.select("col1", "col2", ...)

Или

df = # create dataframe ...
df.rdd.map(lambda row: (row[0], row[1], ...))

Я нахожусь в середине тестирования производительности, поэтому я собираюсь выяснить, что быстрее, но я хотел бы знать, каковы различия в реализации и плюсы / минусы.

5
ezamur 25 Ноя 2016 в 14:57

2 ответа

Лучший ответ

В этом упрощенном примере с DataFrame.select и DataFrame.rdd.map я думаю, что разница может быть почти незначительной.

В конце концов, вы уже загрузили свой набор данных и делаете только проекцию. В конце концов, обоим придется десериализовать данные из столбчатого формата Spark InternalRow, чтобы вычислить результат для действия.

Вы можете проверить, что происходит с DataFrame.select на explain(extended = true), где вы узнаете о физических планах (а также о физическом плане).

scala> spark.version
res4: String = 2.1.0-SNAPSHOT

scala> spark.range(5).select('id).explain(extended = true)
== Parsed Logical Plan ==
'Project [unresolvedalias('id, None)]
+- Range (0, 5, step=1, splits=Some(4))

== Analyzed Logical Plan ==
id: bigint
Project [id#17L]
+- Range (0, 5, step=1, splits=Some(4))

== Optimized Logical Plan ==
Range (0, 5, step=1, splits=Some(4))

== Physical Plan ==
*Range (0, 5, step=1, splits=Some(4))

Сравните физический план (т.е. SparkPlan) с тем, что вы делаете с rdd.map (по toDebugString), и вы поймете, что может быть «лучше».

scala> spark.range(5).rdd.toDebugString
res5: String =
(4) MapPartitionsRDD[8] at rdd at <console>:24 []
 |  MapPartitionsRDD[7] at rdd at <console>:24 []
 |  MapPartitionsRDD[6] at rdd at <console>:24 []
 |  MapPartitionsRDD[5] at rdd at <console>:24 []
 |  ParallelCollectionRDD[4] at rdd at <console>:24 []

(опять же, в этом надуманном примере я думаю, что нет победителя - оба они максимально эффективны).

Обратите внимание, что DataFrame на самом деле является Dataset[Row], который использует RowEncoder для кодирования (т. Е. Сериализации) данных в столбчатый двоичный формат InternalRow. Если бы вы выполняли больше операторов в конвейере, вы могли бы получить гораздо лучшую производительность, придерживаясь Dataset, чем RDD, только потому, что низкоуровневые закулисные оптимизации логического плана запроса и столбчатый двоичный файл формат.

Есть много оптимизаций, и попытки превзойти их часто могут привести к потере вашего времени. Вам нужно знать внутреннее устройство Spark наизусть, чтобы получить лучшую производительность (и цена, безусловно, будет удобочитаемостью).

В этом есть много всего, и я настоятельно рекомендую посмотреть доклад Германа ван Ховелла Глубокое погружение в оптимизатор Catalyst знать и ценить все оптимизации.

Я считаю, что это ... «Держитесь подальше от RDD, если вы не знаете, что делаете» .

1
Jacek Laskowski 26 Ноя 2016 в 08:00

RDD - это просто графическая линия преобразований и действий.

DataFrame имеет логический план, который внутренне оптимизируется оптимизатором логических запросов Catalyst перед выполнением действия.

Что это значит в вашем случае?

Если у вас есть DataFrame, вы должны использовать select - любая дополнительная работа, такая как фильтрация, объединение и т. Д., Будет оптимизирована. Оптимизированный DataFrame может быть в 10 раз быстрее, чем простой RDD. Другими словами, перед выполнением select Spark попытается сделать запрос быстрее. Это не будет сделано при использовании dataFrame.rdd.map ()

Еще одно: значение rdd вычисляется лениво:

lazy val rdd: RDD[T] = {
    val objectType = exprEnc.deserializer.dataType
    val deserialized = CatalystSerde.deserialize[T](logicalPlan)
    sparkSession.sessionState.executePlan(deserialized).toRdd.mapPartitions { rows =>
      rows.map(_.get(0, objectType).asInstanceOf[T])
    }
  }

Таким образом, Spark будет использовать RDD, отображать и транслировать контент. DAG обеих версий будет почти одинаковым в запросе, как и рассматриваемый, поэтому производительность будет аналогичной. Однако в более сложных случаях преимущества использования наборов данных будут очень заметны: как написали специалисты компании Spark PMC в блоге Databricks, наборы данных могут быть даже в 100 раз быстрее после оптимизации с помощью Catalyst.

Имейте в виду, что DataFrame = Dataset [Row], и он использует RDD в фоновом режиме, но график RDD создается после оптимизации

Примечание . Spark объединяет API. Spark ML теперь ориентирован на DataFrame, старый API использовать не следует. Потоковая передача переходит на структурированную потоковую передачу. Поэтому, даже если у вас не будет значительного улучшения производительности в вашем случае, рассмотрите возможность использования DataFrames. Это было бы лучшим решением для будущего развития и, конечно, будет быстрее, чем при использовании простого RDD.

2
T. Gawęda 26 Ноя 2016 в 04:47