Потому что Encoders
жертвует общностью ради производительности. Идея не нова. Почему Kryo быстрее сериализации Java? По той же причине. Посмотрите на эту запись:
scala> val spark = SparkSession.builder.config("spark.serializer", "org.apache.spark.serializer.JavaSerializer").getOrCreate()
spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@1ed28f57
scala> val map = Map[String, Int]("foo" -> 1).withDefaultValue(0)
map: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)
scala> map("bar")
res1: Int = 0
scala> val mapSerDe = spark.sparkContext.parallelize(Seq(map)).first
mapSerDe: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)
scala> mapSerDe("bar")
res2: Int = 0
по сравнению с
scala> val spark = SparkSession.builder.config("spark.serializer", "org.apache.spark.serializer.KryoSerializer").getOrCreate()
spark: org.apache.spark.sql.SparkSession = org.apache.spark.sql.SparkSession@5cef3456
scala> val map = Map[String, Int]("foo" -> 1).withDefaultValue(0)
map: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)
scala> map("bar")
res7: Int = 0
scala> val mapSerDe = spark.sparkContext.parallelize(Seq(map)).first
mapSerDe: scala.collection.immutable.Map[String,Int] = Map(foo -> 1)
scala> mapSerDe("bar")
java.util.NoSuchElementException: key not found: bar
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:59)
... 48 elided
(я не смог найти точный пост, но идея этого примера взята из списка разработчиков).
Как видите, Kryo, хотя и быстрее, не обрабатывает все возможные случаи. Он фокусируется на наиболее распространенных и делает это правильно.
Spark Encoders
делает то же самое, но еще менее общее. Если вы поддерживаете только 16 типов или около того и не заботитесь о совместимости (должно быть с реальными библиотеками сериализации), у вас есть много возможностей для оптимизации.
Нет необходимости во взаимодействии, что позволяет вам двигаться еще дальше. Кодеры для атомарных типов - это просто идентичность. Никаких преобразований вообще не нужно.
Знание схемы, как объяснено himanshuIIITian , является еще одним фактором.
Почему это важно? Потому что, имея четко определенную форму, позволяет оптимизировать сериализацию и хранение. Если вы знаете, что ваши данные структурированы, вы можете переключаться между измерениями - вместо гетерогенных строк, которые дорого хранить и получать доступ, вы можете использовать столбчатое хранилище.
Как только данные сохраняются в столбцах, вы открываете совершенно новый набор возможностей оптимизации:
- Доступ к данным в полях с фиксированным размером чрезвычайно быстр, потому что вы можете напрямую получить доступ к определенному адресу (помните все волнения, связанные с внешней кучей / собственной памятью / вольфрамом?).
- Вы можете использовать широкий спектр методов сжатия и кодирования, чтобы минимизировать размер данных.
Эти идеи тоже не новы. Столбчатые базы данных, форматы хранения (например, Parquet) или современные форматы сериализации, предназначенные для аналитики (например, Arrow), используют те же идеи и часто продвигают их еще дальше (обмен данными без копирования).
К сожалению, кодеры не являются серебряной пулей. Хранение нестандартного объекта является беспорядком , набор кодировщиков может быть очень неэффективным .