Scala 嵌套方法的成本
在Scala中,可以在其他方法中定义方法。这将其使用范围限制在定义块内部。我使用它们来提高使用几个高阶函数的代码的可读性。与匿名函数文字不同,这允许我在传递它们之前为它们指定有意义的名称 例如:Scala 嵌套方法的成本,scala,methods,Scala,Methods,在Scala中,可以在其他方法中定义方法。这将其使用范围限制在定义块内部。我使用它们来提高使用几个高阶函数的代码的可读性。与匿名函数文字不同,这允许我在传递它们之前为它们指定有意义的名称 例如: class AggregatedPerson extends HashSet[PersonRecord] { def mostFrequentName: String = { type NameCount = (String, Int) def moreFirst(a: NameCo
class AggregatedPerson extends HashSet[PersonRecord] {
def mostFrequentName: String = {
type NameCount = (String, Int)
def moreFirst(a: NameCount, b: NameCount) = a._2 > b._2
def countOccurrences(nameGroup: (String, List[PersonRecord])) =
(nameGroup._1, nameGroup._2.size)
iterator.toList.groupBy(_.fullName).
map(countOccurrences).iterator.toList.
sortWith(moreFirst).head._1
}
}
我应该注意的嵌套方法定义是否会导致运行时开销
闭包的答案是否不同?在编译过程中,嵌套函数是
moveFirst
,countoccurrences
被移出到与mostFrequentName
相同的级别。它们得到编译器合成的名称:moveFirst$1
和countoccurrences$1
此外,当您在没有参数列表的情况下引用其中一个方法时,它将被提升到函数中。所以map(countoccurrences)
与编写map((a:(String,List[PersonRecord])=>countoccurrences(a))是一样的。这个匿名函数被编译成一个单独的类AggregatedPerson$$anonfun$mostFrequentName$2
,它只不过是转发到countoccurrencess$
作为旁注,将方法提升为函数的过程称为Eta扩展。如果在预期函数类型的上下文中忽略参数列表(如示例中),或者使用
代替整个参数列表,或者代替每个参数(val f1=countoccurrences;val f2=countoccurrences(
)
如果代码直接在闭包中,那么堆栈中的方法调用将减少一个,生成的合成方法也将减少一个。在大多数情况下,这对性能的影响可能为零
<>我发现它是构造代码的一个非常有用的工具,并且考虑到你的例子非常地道的Scala。
另一个有用的工具是使用小块初始化val:
val a = {
val temp1, temp2 = ...
f(temp1, temp2)
}
您可以使用scalac-print
查看Scala代码是如何转换为JVM所需的格式的。以下是程序的输出:
[[syntax trees at end of cleanup]]// Scala source: nested-method.scala
package <empty> {
class AggregatedPerson extends scala.collection.mutable.HashSet with ScalaObject {
def mostFrequentName(): java.lang.String = AggregatedPerson.this.iterator().toList().groupBy({
(new AggregatedPerson$$anonfun$mostFrequentName$1(AggregatedPerson.this): Function1)
}).map({
{
(new AggregatedPerson$$anonfun$mostFrequentName$2(AggregatedPerson.this): Function1)
}
}, collection.this.Map.canBuildFrom()).$asInstanceOf[scala.collection.MapLike]().iterator().toList().sortWith({
{
(new AggregatedPerson$$anonfun$mostFrequentName$3(AggregatedPerson.this): Function2)
}
}).$asInstanceOf[scala.collection.IterableLike]().head().$asInstanceOf[Tuple2]()._1().$asInstanceOf[java.lang.String]();
final def moreFirst$1(a: Tuple2, b: Tuple2): Boolean = scala.Int.unbox(a._2()).>(scala.Int.unbox(b._2()));
final def countOccurrences$1(nameGroup: Tuple2): Tuple2 = new Tuple2(nameGroup._1(), scala.Int.box(nameGroup._2().$asInstanceOf[scala.collection.SeqLike]().size()));
def this(): AggregatedPerson = {
AggregatedPerson.super.this();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class AggregatedPerson$$anonfun$mostFrequentName$1 extends scala.runtime.AbstractFunction1 {
final def apply(x$1: PersonRecord): java.lang.String = x$1.fullName();
final <bridge> def apply(v1: java.lang.Object): java.lang.Object = AggregatedPerson$$anonfun$mostFrequentName$1.this.apply(v1.$asInstanceOf[PersonRecord]());
def this($outer: AggregatedPerson): AggregatedPerson$$anonfun$mostFrequentName$1 = {
AggregatedPerson$$anonfun$mostFrequentName$1.super.this();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class AggregatedPerson$$anonfun$mostFrequentName$2 extends scala.runtime.AbstractFunction1 {
final def apply(nameGroup: Tuple2): Tuple2 = AggregatedPerson$$anonfun$mostFrequentName$2.this.$outer.countOccurrences$1(nameGroup);
<synthetic> <paramaccessor> private[this] val $outer: AggregatedPerson = _;
final <bridge> def apply(v1: java.lang.Object): java.lang.Object = AggregatedPerson$$anonfun$mostFrequentName$2.this.apply(v1.$asInstanceOf[Tuple2]());
def this($outer: AggregatedPerson): AggregatedPerson$$anonfun$mostFrequentName$2 = {
if ($outer.eq(null))
throw new java.lang.NullPointerException()
else
AggregatedPerson$$anonfun$mostFrequentName$2.this.$outer = $outer;
AggregatedPerson$$anonfun$mostFrequentName$2.super.this();
()
}
};
@SerialVersionUID(0) @serializable final <synthetic> class AggregatedPerson$$anonfun$mostFrequentName$3 extends scala.runtime.AbstractFunction2 {
final def apply(a: Tuple2, b: Tuple2): Boolean = AggregatedPerson$$anonfun$mostFrequentName$3.this.$outer.moreFirst$1(a, b);
<synthetic> <paramaccessor> private[this] val $outer: AggregatedPerson = _;
final <bridge> def apply(v1: java.lang.Object, v2: java.lang.Object): java.lang.Object = scala.Boolean.box(AggregatedPerson$$anonfun$mostFrequentName$3.this.apply(v1.$asInstanceOf[Tuple2](), v2.$asInstanceOf[Tuple2]()));
def this($outer: AggregatedPerson): AggregatedPerson$$anonfun$mostFrequentName$3 = {
if ($outer.eq(null))
throw new java.lang.NullPointerException()
else
AggregatedPerson$$anonfun$mostFrequentName$3.this.$outer = $outer;
AggregatedPerson$$anonfun$mostFrequentName$3.super.this();
()
}
}
}
[[清理结束时的语法树]]//Scala源代码:nested-method.Scala
包装{
类AggregatedPerson使用ScalaObject扩展scala.collection.mutable.HashSet{
def mostFrequentName():java.lang.String=AggregatedPerson.this.iterator().toList().groupBy({
(新的AggregatedPerson$$anonfun$mostFrequentName$1(AggregatedPerson.this):函数1)
}).地图({
{
(新的AggregatedPerson$$anonfun$mostFrequentName$2(AggregatedPerson.this):函数1)
}
},collection.this.Map.canBuildFrom()).$asInstanceOf[scala.collection.MapLike]().iterator().toList().sortWith({
{
(新的AggregatedPerson$$anonfun$mostFrequentName$3(AggregatedPerson.this):函数2)
}
}).$asInstanceOf[scala.collection.IterableLike]().head().$asInstanceOf[Tuple2]()。\u 1().$asInstanceOf[java.lang.String]();
final def moreFirst$1(a:Tuple2,b:Tuple2):Boolean=scala.Int.unbox(a._2())。>(scala.Int.unbox(b._2());
final def countoccurrents$1(nameGroup:Tuple2):Tuple2=新的Tuple2(nameGroup.\u 1(),scala.Int.box(nameGroup.\u 2().$asInstanceOf[scala.collection.SeqLike]().size());
def this():AggregatedPerson={
AggregatedPerson.super.this();
()
}
};
@SerialVersionUID(0)@serializable final class AggregatedPerson$$anonfun$mostFrequentName$1扩展了scala.runtime.AbstractFunction1{
最终def apply(x$1:PersonRecord):java.lang.String=x$1.fullName();
final def apply(v1:java.lang.Object):java.lang.Object=AggregatedPerson$$anonfun$mostFrequentName$1.this.apply(v1.$asInstanceOf[PersonRecord]());
定义此($outer:AggregatedPerson):AggregatedPerson$$anonfun$mostFrequentName$1={
AggregatedPerson$$anonfun$mostFrequentName$1.super.this();
()
}
};
@SerialVersionUID(0)@serializable final class AggregatedPerson$$anonfun$mostFrequentName$2扩展了scala.runtime.AbstractFunction1{
最终定义应用(名称组:Tuple2):Tuple2=AggregatedPerson$$anonfun$mostFrequentName$2.this.$outer.CountOccents$1(名称组);
private[this]val$outer:AggregatedPerson=\uux;
final def apply(v1:java.lang.Object):java.lang.Object=AggregatedPerson$$anonfun$mostFrequentName$2.this.apply(v1.asInstanceOf[Tuple2]());
定义此($outer:AggregatedPerson):AggregatedPerson$$anonfun$mostFrequentName$2={
如果($outer.eq(null))
抛出新的java.lang.NullPointerException()
其他的
AggregatedPerson$$anonfun$mostFrequentName$2。此。$outer=$outer;
AggregatedPerson$$anonfun$mostFrequentName$2.super.this();
()
}
};
@SerialVersionUID(0)@serializable final class AggregatedPerson$$anonfun$mostFrequentName$3扩展了scala.runtime.AbstractFunction2{
final def apply(a:Tuple2,b:Tuple2):Boolean=AggregatedPerson$$anonfun$mostFrequentName$3.this.$outer.moreFirst$1(a,b);
private[this]val$outer:AggregatedPerson=\uux;
final def apply(v1:java.lang.Object,v2:java.lang.Object):java.lang.Object=scala.Boolean.box(AggregatedPerson$$anonfun$mostFrequentName$3.this.apply(v1.$asInstanceOf[Tuple2](),v2.$asInstanceOf[Tuple2]());
定义此($outer:AggregatedPerson):AggregatedPerson$$anonfun$mostFrequentName$3={
如果($outer.eq(null))
抛出新的java.lang.NullPointerException()
其他的
AggregatedPerson$$anonfun$mostFrequentName$3。此。$outer=$outer;
AggregatedPerson$$anonfun$mostFrequentName$3.super.this();
()
}
}
}
运行时成本很低。您可以在此处观察到这一点(抱歉代码太长):
因此,底线是:在简单的情况下,嵌套函数确实不会对您造成任何伤害——JVM会发现调用可以内联(因此raw
和internal
给出相同的时间)。如果您采取更具功能性的方法,函数调用不能完全忽略,但是tim
object NestBench {
def countRaw() = {
var sum = 0
var i = 0
while (i<1000) {
sum += i
i += 1
var j = 0
while (j<1000) {
sum += j
j += 1
var k = 0
while (k<1000) {
sum += k
k += 1
sum += 1
}
}
}
sum
}
def countClosure() = {
var sum = 0
var i = 0
def sumI {
sum += i
i += 1
var j = 0
def sumJ {
sum += j
j += 1
var k = 0
def sumK {
def sumL { sum += 1 }
sum += k
k += 1
sumL
}
while (k<1000) sumK
}
while (j<1000) sumJ
}
while (i<1000) sumI
sum
}
def countInner() = {
var sum = 0
def whileI = {
def whileJ = {
def whileK = {
def whileL() = 1
var ksum = 0
var k = 0
while (k<1000) { ksum += k; k += 1; ksum += whileL }
ksum
}
var jsum = 0
var j = 0
while (j<1000) {
jsum += j; j += 1
jsum += whileK
}
jsum
}
var isum = 0
var i = 0
while (i<1000) {
isum += i; i += 1
isum += whileJ
}
isum
}
whileI
}
def countFunc() = {
def summer(f: => Int)() = {
var sum = 0
var i = 0
while (i<1000) {
sum += i; i += 1
sum += f
}
sum
}
summer( summer( summer(1) ) )()
}
def nsPerIteration(f:() => Int): (Int,Double) = {
val t0 = System.nanoTime
val result = f()
val t1 = System.nanoTime
(result , (t1-t0)*1e-9)
}
def main(args: Array[String]) {
for (i <- 1 to 5) {
val fns = List(countRaw _, countClosure _, countInner _, countFunc _)
val labels = List("raw","closure","inner","func")
val results = (fns zip labels) foreach (fl => {
val x = nsPerIteration( fl._1 )
printf("Method %8s produced %d; time/it = %.3f ns\n",fl._2,x._1,x._2)
})
}
}
}
scala> NestBench.main(Array[String]())
Method raw produced -1511174132; time/it = 0.422 ns
Method closure produced -1511174132; time/it = 2.376 ns
Method inner produced -1511174132; time/it = 0.402 ns
Method func produced -1511174132; time/it = 0.836 ns
Method raw produced -1511174132; time/it = 0.418 ns
Method closure produced -1511174132; time/it = 2.410 ns
Method inner produced -1511174132; time/it = 0.399 ns
Method func produced -1511174132; time/it = 0.813 ns
Method raw produced -1511174132; time/it = 0.411 ns
Method closure produced -1511174132; time/it = 2.372 ns
Method inner produced -1511174132; time/it = 0.399 ns
Method func produced -1511174132; time/it = 0.813 ns
Method raw produced -1511174132; time/it = 0.411 ns
Method closure produced -1511174132; time/it = 2.370 ns
Method inner produced -1511174132; time/it = 0.399 ns
Method func produced -1511174132; time/it = 0.815 ns
Method raw produced -1511174132; time/it = 0.412 ns
Method closure produced -1511174132; time/it = 2.357 ns
Method inner produced -1511174132; time/it = 0.400 ns
Method func produced -1511174132; time/it = 0.817 ns
import com.google.caliper.SimpleBenchmark
class Benchmark extends SimpleBenchmark {
def timeRaw(reps: Int) = {
var i = 0
var result = 0L
while (i < reps) {
result += 0xc37e ^ (i * 0xd5f3)
i = i + 1
}
result
}
def normal(i: Int): Long = 0xc37e ^ (i * 0xd5f3)
def timeNormal(reps: Int) = {
var i = 0
var result = 0L
while (i < reps) {
result += normal(i)
i = i + 1
}
result
}
def timeInner(reps: Int) = {
def inner(i: Int): Long = 0xc37e ^ (i * 0xd5f3)
var i = 0
var result = 0L
while (i < reps) {
result += inner(i)
i = i + 1
}
result
}
def timeClosure(reps: Int) = {
var i = 0
var result = 0L
val closure = () => result += 0xc37e ^ (i * 0xd5f3)
while (i < reps) {
closure()
i = i + 1
}
result
}
def normal(i: Int, j: Int, k: Int, l: Int): Long = i ^ j ^ k ^ l
def timeUnboxed(reps: Int) = {
var i = 0
var result = 0L
while (i < reps) {
result += normal(i,i,i,i)
i = i + 1
}
result
}
val closure = (i: Int, j: Int, k: Int, l: Int) => (i ^ j ^ k ^ l).toLong
def timeBoxed(reps: Int) = {
var i = 0
var result = 0L
while (i < reps) {
closure(i,i,i,i)
i = i + 1
}
result
}
}
benchmark ns linear runtime
Normal 0.576 =
Raw 0.576 =
Inner 0.576 =
Closure 0.532 =
Unboxed 0.893 =
Boxed 15.210 ==============================
benchmark ns linear runtime
Raw 0.574 =
Normal 0.576 =
Inner 0.575 =
Closure 0.645 =
Unboxed 0.889 =
Boxed 15.107 ==============================
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)
Scala compiler version 2.10.3 -- Copyright 2002-2013, LAMP/EPFL