【问题标题】:How does Scala compiler handle unused variable values?Scala 编译器如何处理未使用的变量值?
【发布时间】:2015-09-04 07:30:47
【问题描述】:

使用 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 的值感兴趣,因此最后一行中的映射步骤省略了键。实际上,这是 rdd2rdd1 之间的交集,出于效率原因,与 Spark 的 join() 执行。

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

【问题讨论】:

    标签: performance scala memory apache-spark


    【解决方案1】:

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

    您可以使用(String, Unit),而不是映射到(String, String)

    rdd1.map(s => (s,()))
    

    您所做的基本上是基于rdd1rdd2 过滤器。如果rdd1明显小于rdd2,另一种方法是将rdd1的数据表示为广播变量而不是RDD,并简单地过滤rdd2。这避免了任何 shuffle 或 reduce 阶段,因此可能更快,但仅当 rdd1 的数据足够小以适合每个节点时才有效。

    编辑:

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

    object size extends App {
    
      (1 to 1000000).map(i => ("foo"+i, ()))
      val input = readLine("prompt> ")
    }
    

    object size extends App {
    
      (1 to 1000000).map(i => ("foo"+i, "foo"+i))
      val input = readLine("prompt> ")
    }
    

    使用这个问题How to check heap usage of a running JVM from the command line? 中描述的jstat 命令,第一个版本使用的堆比后者少得多。

    编辑 2:

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

    Spark 默认使用 Java 序列化。考虑以下几点:

    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
    

    �� sr Main$Bar+a!N��b L at Ljava/lang/String;L bq ~ xpt abcdeft xyz
    

    【讨论】:

    • 听起来很合理,谢谢。但是,与原始字符串变体相比,我仍然不确定存储对 Unit 的引用如何节省大量内存。是吗?
    • 已扩展我的答案以涵盖该主题
    • 但是在原始问题中没有创建新的字符串。对 String 的引用与对 () 的引用大小相同。
    • 很难知道 Spark 在幕后做了什么(好吧,就像对源代码的详细阅读一样困难),但连接将涉及一个 shuffle 阶段,需要对数据进行序列化,并且在在那一点上,我希望这两个引用成为两个单独的序列化字符串。
    • 那么它是如何序列化 Unit 值的呢?为了这个目的,它当然需要某种(抽象)类型的序列化,以防在稍后阶段实际需要该值。
    猜你喜欢
    • 2017-10-05
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多