[英]Scala: call by name and call by value performance
Consider the following code: 请考虑以下代码:
class A(val name: String)
Compare the two wrappers of A
: 比较A
的两个包装器:
class B(a: A){
def name: String = a.name
}
and 和
class B1(a: A){
val name: String = a.name
}
B
and B1
have the same functionality. B
和B1
具有相同的功能。 How is the memory efficiency and computation efficiency comparison between them? 它们之间的内存效率和计算效率如何比较? Will Scala compiler view them as the same thing? Scala编译器会将它们视为同一个东西吗?
First, I'll say that micro optimizations questions are usually tricky to answer for their lack of context. 首先,我会说微观优化问题通常很难回答他们缺乏背景。 Additionally, this question has nothing to do with call by name or call by value, as non of your examples are call by name. 此外,此问题与按名称调用或按值调用无关,因为您的示例中没有按名称调用。
Now, let's compile your code with scalac -Xprint:typer
and see what gets emitted: 现在,让我们用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
};
In class B
, we hold a reference to a
, and have a method name
which calls the name
value on A
. 在课堂上B
,我们坚持一个参考a
,并有一个方法name
它调用name
价值A
。
In class B1
, we store name
locally, since it is a value of B1
directly, not a method. 在B1
类中,我们在本地存储name
,因为它直接是B1
的值,而不是方法。 By definition, val
declarations in have a method generated for them and that is how they're accessed. 根据定义, val
声明中有一个为它们生成的方法,这就是它们的访问方式。
This boils down to the fact that B1
holds an additional reference to the name
string allocated by A
. 这可以归结为B1
保存了对A
分配的name
字符串的附加引用。 Is this significant in any way from a performance perspective? 从性能角度来看,这有何重要意义? I don't know. 我不知道。 It looks negligible to me under general question you've posted, but I wouldn't be to bothered with this unless you've profiled your application and found this a bottleneck. 在你发布的一般性问题下,它对我来说可以忽略不计,但我不会为此烦恼, 除非你已经分析了你的应用程序并发现这是一个瓶颈。
Lets take this one step further, and run a simple JMH micro benchmark on this: 让我们更进一步,并在此基础上运行一个简单的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
We see that call times are identical, since in both times we're invoking a method. 我们看到调用时间是相同的,因为在这两个时间我们都在调用一个方法。 One thing we can notice is that the throughput on B
is higher, why is that? 我们可以注意到的一件事是B
上的吞吐量更高,为什么呢? Lets look at the byte code: 让我们看一下字节码:
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: B1:
public java.lang.String name();
Code:
0: aload_0
1: getfield #19 // Field name:Ljava/lang/String;
4: areturn
It isn't trivial to understand why a getfield
would be slower than a invokevirtual
, but in the end the JIT may inline the getter call to name
. 理解为什么getfield
比invokevirtual
慢,但最终JIT可能会将getter调用内联到name
这并非易事。 This goes to show you that you should take nothing for granted, benchmark everything. 这表明你不应该把任何事情视为理所当然,对所有事情进行基准测试。
Code for test: 测试代码:
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.