将身份功能映射到Scala集合的复杂性?

时间:2016-09-18 03:00:44

标签: scala scala-collections scala-compiler

当我使用identity方法将Scala预定义map函数应用于集合时,原始集合将保持不变。但是,编译器是否足够智能,只需将未更改的集合作为O(1)操作返回?或者,身份功能是否仍会应用于原始集合中的每个元素,从而产生O(n)操作?

4 个答案:

答案 0 :(得分:4)

检查情况并非如此,这非常简单。首先制作一个包含可能优化形式的测试文件,然后使用scalac(带或不带-optimise)进行编译

/// TestMap.scala
object TestMap {
  def mapOption[T](o: Option[T]): Option[T] = o.map(identity)
  def mapList[T](l: List[T]): List[T] = l.map(identity)
  def mapSeq[T](l: Seq[T]): Seq[T] = l.map(identity)
}

然后,检查javap -c TestMap.class您可以看到在mapmapSeqmapListmapOption之后没有优化任何优化:

Compiled from "TestMap.scala"
public final class TestMap {
  public static <T extends java/lang/Object> scala.collection.Seq<T> mapSeq(scala.collection.Seq<T>);
    Code:
       0: getstatic     #16                 // Field TestMap$.MODULE$:LTestMap$;
       3: aload_0       
       4: invokevirtual #18                 // Method TestMap$.mapSeq:(Lscala/collection/Seq;)Lscala/collection/Seq;
       7: areturn       

  public static <T extends java/lang/Object> scala.collection.immutable.List<T> mapList(scala.collection.immutable.List<T>);
    Code:
       0: getstatic     #16                 // Field TestMap$.MODULE$:LTestMap$;
       3: aload_0       
       4: invokevirtual #22                 // Method TestMap$.mapList:(Lscala/collection/immutable/List;)Lscala/collection/immutable/List;
       7: areturn       

  public static <T extends java/lang/Object> scala.Option<T> mapOption(scala.Option<T>);
    Code:
       0: getstatic     #16                 // Field TestMap$.MODULE$:LTestMap$;
       3: aload_0       
       4: invokevirtual #26                 // Method TestMap$.mapOption:(Lscala/Option;)Lscala/Option;
       7: areturn  

更简单地说,这种优化在具有副作用的语言中并没有很好地扩展(另一方面,在Haskell中,这种事情经常发生)。编译器应该优化l.map(x => { println(x); x })l吗?

答案 1 :(得分:2)

Rex Kerr的handy Thyme utility证实了Alec的调查结果。 identity运行时大致与集合大小成比例。

val smallC = Vector.tabulate(90)(_*2)
val bigC = Vector.tabulate(900)(_*2)

val th = ichi.bench.Thyme.warmed(verbose = print)
th.pbenchOffWarm("A vs. B")(th.Warm(smallC.map(identity)))(th.Warm(bigC.map(identity)))
Benchmark comparison (in 4.694 s): A vs. B
Significantly different (p ~= 0)
  Time ratio:    9.31267   95% CI 9.25599 - 9.36935   (n=20)
    First     1.492 us   95% CI 1.487 us - 1.496 us
    Second    13.89 us   95% CI 13.82 us - 13.96 us

答案 2 :(得分:2)

  

当我使用identity方法将Scala预定义map函数应用于集合时,原始集合将保持不变。

不,不是。返回具有相同内容的 new 集合。构建这个新集合通常是O(n)。

  

但是,编译器是否足够智能,只需将未更改的集合作为O(1)操作返回?或者,身份功能是否仍会应用于原始集合中的每个元素,从而产生O(n)操作?

为了执行这样的优化,编译器必须确定要应用的函数在扩展上等于标识函数。此问题称为功能问题,并且已知不可判定。 (例如,可以使用暂停问题证明这一点。)

当然, 可以针对特定函数Predef.identity进行优化,而不仅仅是任何身份函数。但Scala编译器设计人员并不喜欢这种一次性的特殊情况优化,只能帮助标准库代码。他们更喜欢使用所有代码的一般优化。

答案 3 :(得分:0)

测量执行时间似乎表明身份函数是O(n):

this link

开始测量代码执行时间的功能
def time[R](block: => R): R = {  
    val t0 = System.nanoTime()
    val result = block    // call-by-name
    val t1 = System.nanoTime()
    println("Elapsed time: " + (t1 - t0) + "ns")
    result
}

time {(1 to 100000000).map(identity)}  // Elapsed time: 8893077396ns

time {(1 to 10).map(identity)} // Elapsed time: 341129ns

// while only creation of range takes similar order of magnitude times. 

time {(1 to 10)}  // Elapsed time: 30250ns

time {(1 to 100000000)} // Elapsed time: 32351ns