Spark の RDD は、物理的な計画も構築し、同じ段階で複数の変換を結合/最適化できます。RDD に対する DataSet の利点は何ですか?
RDD を使用する場合、作成したものが得られます。特定の変換は連鎖によって最適化されますが、実行計画は DAG の直接変換です。例えば:
rdd.mapPartitions(f).mapPartitions(g).mapPartitions(h).shuffle()
ここshuffle
で、 は任意のシャッフル変換 ( *byKey
、repartition
など) です。3 つすべてmapPartitions
( map
、flatMap
、filter
) は、中間オブジェクトを作成せずにチェーンされますが、再配置することはできません。
それと比較して、Datasets
はるかに制限的なプログラミング モデルを使用しますが、次のような多くの手法を使用して実行を最適化できます。
選択 ( filter
) プッシュダウン。たとえば、次の場合:
df.withColumn("foo", col("bar") + 1).where(col("bar").isNotNull())
次のように実行できます。
df.where(col("bar").isNotNull()).withColumn("foo", col("bar") + 1)
初期の予測 ( select
) と削除。例えば:
df.withColumn("foo", col("bar") + 1).select("foo", "bar")
次のように書き換えることができます。
df.select("foo", "bar").withColumn("foo", col("bar") + 1)
古いデータのフェッチと受け渡しを避けるため。極端な場合、特定の変換を完全に排除できます。
df.withColumn("foo", col("bar") + 1).select("bar")
に最適化できます
df.select("bar")
これらの最適化は、次の 2 つの理由で可能です。
- 複雑で信頼性の低い静的コード分析なしで依存関係分析を可能にする制限付きデータ モデル。
- 明確な演算子のセマンティクス。演算子には副作用がなく、決定論的なものと非決定論的なものを明確に区別しています。
明確にするために、次のデータ モデルがあるとします。
case class Person(name: String, surname: String, age: Int)
val people: RDD[Person] = ???
そして、21 歳以上のすべての人の姓を取得したいと考えています。これRDD
を次のように表現できます。
people
.map(p => (p.surname, p.age)) // f
.filter { case (_, age) => age > 21 } // g
ここで、いくつかの質問を自問してみましょう。
age
の入力と変数f
の関係は何ですか?age
g
- and thenはand
f
theng
と同じですか?g
f
f
とg
副作用は無料ですか?
答えは人間の読者にとっては明らかですが、仮想のオプティマイザーにとってはそうではありません。Dataframe
バージョンとの比較:
people.toDF
.select(col("surname"), col("age")) // f'
.where(col("age") > 21) // g'
その答えは、オプティマイザーと人間の読者の両方にとって明らかです。
これは、静的に型付けされたものDatasets
( Spark 2.0 Dataset と DataFrame ) を使用する場合に、さらにいくつかの結果をもたらします。
DataSet はより高度な型付けを取得しましたか?
- いいえ - 最適化を気にする場合。最も高度な最適化は制限されて
Dataset[Row]
おり、現時点では複雑な型階層をエンコードすることはできません。
- おそらく - Kryo または Java エンコーダーのオーバーヘッドを受け入れる場合。
「ベクトル化された操作」とはどういう意味ですか?
最適化のコンテキストでは、通常、ループのベクトル化/ループの展開を意味します。Spark SQL はコード生成を使用して、ベクトル化された命令セットを利用するためにさらに最適化できる高レベルの変換のコンパイラーに適したバージョンを作成します。
私が理解しているように、DataSet の低メモリ管理 = 高度なシリアル化。
ではない正確に。ネイティブ割り当てを使用する最大の利点は、ガベージ コレクター ループを回避できることです。ガベージ コレクションは Spark の制限要因であることが非常に多いため、特に大きなデータ構造を必要とするコンテキスト (シャッフルの準備など) では、これは大きな改善です。
もう 1 つの重要な側面は、効率的な圧縮 (潜在的にメモリ フットプリントの削減) と圧縮データに対する最適化された操作を可能にする列型ストレージです。
一般に、plain で手作りのコードを使用して、まったく同じタイプの最適化を適用できますRDDs
。結局のところDatasets
、によって支えられていRDDs
ます。違いは、どれだけの労力がかかるかだけです。
- 手作りの実行計画の最適化は、比較的簡単に実現できます。
- コードをコンパイラに適したものにするには、より深い知識が必要であり、エラーが発生しやすく、冗長です。
sun.misc.Unsafe
ネイティブのメモリ割り当てを使用することは、気弱な人向けではありません。
そのすべてのメリットにもかかわらず、Dataset
API は普遍的ではありません。特定のタイプの一般的なタスクは、多くのコンテキストでその最適化の恩恵を受けることができますが、RDD の同等物と比較して、まったく改善されないか、パフォーマンスが低下することさえあります。