[英]Scala: call by name and call by value performance
请考虑以下代码:
class A(val name: String)
比较A
的两个包装器:
class B(a: A){
def name: String = a.name
}
和
class B1(a: A){
val name: String = a.name
}
B
和B1
具有相同的功能。 它们之间的内存效率和计算效率如何比较? Scala编译器会将它们视为同一个东西吗?
首先,我会说微观优化问题通常很难回答他们缺乏背景。 此外,此问题与按名称调用或按值调用无关,因为您的示例中没有按名称调用。
现在,让我们用scalac -Xprint:typer
编译你的代码,看看会发出什么:
class B extends scala.AnyRef {
<paramaccessor> private[this] val a: A = _;
def <init>(a: A): B = {
B.super.<init>();
()
};
def name: String = B.this.a.name
};
class B1 extends scala.AnyRef {
<paramaccessor> private[this] val a: A = _;
def <init>(a: A): B1 = {
B1.super.<init>();
()
};
private[this] val name: String = B1.this.a.name;
<stable> <accessor> def name: String = B1.this.name
};
在课堂上B
,我们坚持一个参考a
,并有一个方法name
它调用name
价值A
。
在B1
类中,我们在本地存储name
,因为它直接是B1
的值,而不是方法。 根据定义, val
声明中有一个为它们生成的方法,这就是它们的访问方式。
这可以归结为B1
保存了对A
分配的name
字符串的附加引用。 从性能角度来看,这有何重要意义? 我不知道。 在你发布的一般性问题下,它对我来说可以忽略不计,但我不会为此烦恼, 除非你已经分析了你的应用程序并发现这是一个瓶颈。
让我们更进一步,并在此基础上运行一个简单的JMH微基准:
[info] Benchmark Mode Cnt Score Error Units
[info] MicroBenchClasses.testB1Access thrpt 50 296.291 ± 20.787 ops/us
[info] MicroBenchClasses.testBAccess thrpt 50 303.866 ± 5.435 ops/us
[info] MicroBenchClasses.testB1Access avgt 9 0.004 ± 0.001 us/op
[info] MicroBenchClasses.testBAccess avgt 9 0.003 ± 0.001 us/op
我们看到调用时间是相同的,因为在这两个时间我们都在调用一个方法。 我们可以注意到的一件事是B
上的吞吐量更高,为什么呢? 让我们看一下字节码:
B:
public java.lang.String name();
Code:
0: aload_0
1: getfield #20 // Field a:Lcom/testing/SimpleTryExample$A$1;
4: invokevirtual #22 // Method com/testing/SimpleTryExample$A$1.name:()Ljava/lang/String;
7: areturn
B1:
public java.lang.String name();
Code:
0: aload_0
1: getfield #19 // Field name:Ljava/lang/String;
4: areturn
理解为什么getfield
比invokevirtual
慢,但最终JIT可能会将getter调用内联到name
这并非易事。 这表明你不应该把任何事情视为理所当然,对所有事情进行基准测试。
测试代码:
import java.util.concurrent.TimeUnit
import org.openjdk.jmh.annotations._
/**
* Created by Yuval.Itzchakov on 19/10/2017.
*/
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 3)
@BenchmarkMode(Array(Mode.AverageTime, Mode.Throughput))
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(3)
class MicroBenchClasses {
class A(val name: String)
class B(a: A){
def name: String = a.name
}
class B1(a: A){
val name: String = a.name
}
var b: B = _
var b1: B1 = _
@Setup
def setup() = {
val firstA = new A("yuval")
val secondA = new A("yuval")
b = new B(firstA)
b1 = new B1(secondA)
}
@Benchmark
def testBAccess(): String = {
b.name
}
@Benchmark
def testB1Access(): String = {
b1.name
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.