在大型记录上,Spark StringIndexer.fit非常慢

时间:2018-07-23 19:00:45

标签: apache-spark apache-spark-dataset

我有大数据记录,其格式设置为以下示例:

// +---+------+------+
// |cid|itemId|bought|
// +---+------+------+
// |abc|   123|  true|
// |abc|   345|  true|
// |abc|   567|  true|
// |def|   123|  true|
// |def|   345|  true|
// |def|   567|  true|
// |def|   789| false|
// +---+------+------+

ciditemId是字符串。

有965,964,223条记录。

我正尝试使用cidStringIndexer转换为整数,如下所示:

dataset.repartition(50)
val cidIndexer = new StringIndexer().setInputCol("cid").setOutputCol("cidIndex")
val cidIndexedMatrix = cidIndexer.fit(dataset).transform(dataset)

但是这些代码行非常慢(大约需要30分钟)。问题是它是如此之大,以至于我在那之后再也无能为力了。

我正在使用具有2个节点(61 GB内存)的R4 2XLarge集群的亚马逊EMR集群。

是否可以进一步改善性能?任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:3)

如果列的基数很高,这是预期的行为。作为训练过程的一部分,StringIndexer收集所有标签,并创建标签-索引映射(使用Spark的o.a.s.util.collection.OpenHashMap)。

在最坏的情况下,此过程需要O(N)内存,并且在计算和内存上都很耗费资源。

如果列的基数很高,并且其内容将用作功能,则最好应用FeatureHasher(Spark 2.3或更高版本)。

import org.apache.spark.ml.feature.FeatureHasher

val hasher = new FeatureHasher()
  .setInputCols("cid")
  .setOutputCols("cid_hash_vec")
hasher.transform(dataset)

它不能保证唯一性,并且不可逆,但是对于许多应用程序来说已经足够好了,并且不需要调整过程。

对于不会用作功能的列,您还可以使用hash函数:

import org.apache.spark.sql.functions.hash

dataset.withColumn("cid_hash", hash($"cid"))

答案 1 :(得分:0)

假设:

  • 您打算将cid用作功能(在StringIndexer + OneHotEncoderEstimator之后)
  • 您的数据位于S3中

首先有几个问题:

在不了解更多信息的情况下,我的第一个猜测是您现在不必担心内存,而是先检查并行度。您只有2个R4 2XLarge实例,它们将为您提供:

  • 8个CPU
  • 61GB内存

我个人会尝试:

  • 获取更多实例
  • R4 2XLarge实例与具有更多CPU的其他实例交换

不幸的是,在当前的EMR产品中,只能通过花钱解决问题来实现:

最后,repartition(50)有什么需要? 可能只会引入更多延迟...