Scala编译器如何处理未使用的变量值?

时间:2015-09-04 07:30:48

标签: performance scala memory apache-spark

使用Scala和Spark,我有以下结构:

val rdd1: RDD[String] = ...
val rdd2: RDD[(String, Any)] = ...

val rdd1pairs = rdd1.map(s => (s, s))
val result = rdd2.join(rdd1pairs)
              .map { case (_: String, (e: Any, _)) => e }

rdd1映射到PairRDD的目的是在后续步骤中与rdd2的联接。但是,我实际上只对rdd2的值感兴趣,因此最后一行中的映射步骤省略了键。实际上,出于效率原因,这是使用Spark rdd2执行的rdd1join()之间的交集。

我的问题是指rdd1pairs的键:它们是出于语法原因而创建的(允许连接)在第一个映射步骤中,后来被丢弃而没有任何用法。编译器如何处理这个?在内存消耗方面是否重要,我是否使用字符串s(如示例所示)?我应该用null0替换它以节省一点内存吗?编译器是否实际创建并存储了这些对象(引用),还是注意到它们从未被使用过?

1 个答案:

答案 0 :(得分:3)

在这种情况下,我认为Spark驱动程序将会影响结果,而不是编译器。 Spark是否可以优化其执行管道,以避免创建s的冗余重复。我不确定,但我认为Spark会在内存中创建rdd1pairs

您可以使用(String, String)

,而不是映射到(String, Unit)
rdd1.map(s => (s,()))

您正在做的事情基本上是基于rdd2的{​​{1}}过滤器。如果rdd1明显小于rdd2,则另一种方法是将rdd1的数据表示为广播变量而不是RDD,并简单地过滤rdd1。这可以避免任何混乱或减少阶段,因此可能更快,但仅在rdd2的数据小到足以适合每个节点时才有效。

编辑:

考虑如何使用Unit而不是String节省空间,请考虑以下示例:

rdd1

object size extends App {

  (1 to 1000000).map(i => ("foo"+i, ()))
  val input = readLine("prompt> ")
}

使用此问题How to check heap usage of a running JVM from the command line?中描述的object size extends App { (1 to 1000000).map(i => ("foo"+i, "foo"+i)) val input = readLine("prompt> ") } 命令,第一个版本使用的堆比后者少得多。

编辑2:

jstat实际上是一个没有内容的单例对象,因此从逻辑上讲,它不需要任何序列化。类型定义包含Unit这一事实告诉您,您需要能够反序列化具有Unit类型字段的结构。

Spark默认使用Java Serialization。请考虑以下事项:

Unit

这两个文件的大小相同:

object Main extends App {

  import java.io.{ObjectOutputStream, FileOutputStream}

  case class Foo (a: String, b:String)
  case class Bar (a: String, b:String, c: Unit)

  val str = "abcdef"
  val foo = Foo("abcdef", "xyz")
  val bar = Bar("abcdef", "xyz", ())

  val fos = new FileOutputStream( "foo.obj" )
  val fo = new ObjectOutputStream( fos )
  val bos = new FileOutputStream( "bar.obj" )
  val bo = new ObjectOutputStream( bos )
  fo writeObject foo
  bo writeObject bar
}

�� sr Main$Foo3�,�z \ L at Ljava/lang/String;L bq ~ xpt abcdeft xyz