当我使用identity
方法将Scala预定义map
函数应用于集合时,原始集合将保持不变。但是,编译器是否足够智能,只需将未更改的集合作为O(1)
操作返回?或者,身份功能是否仍会应用于原始集合中的每个元素,从而产生O(n)
操作?
答案 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
您可以看到在map
到mapSeq
,mapList
或mapOption
之后没有优化任何优化:
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):
开始测量代码执行时间的功能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